Define globals when bundling to umd or commonjs - javascript

I have a client-side application which makes use of some browser global properties like Element or document.
I'd like to run my application in node.js as well and currently I am overriding those globals with the domino dom implementation in my server like so:
const domino = require("domino");
const domimpl = domino.createDOMImplementation();
const document = domimpl.createHTMLDocument();
Object.assign(global, Element: domino.impl.Element, document};
const myApp = require('my-app');
I am currently using rollup to bundle different versions of my-app, how can I have rollup do this for me automatically for the _server version of my-app so consumers of my-app don't have to do that?
I was thinking of writing my own rollup plugin but I feel like overriding globals seems like a common practice.

TLDR; Use separate entry file instead of a rollup plugin.
Simply add the following instead of a rollup plugin
if (typeof window ==== "undefined") {
const domino = require("domino");
const domimpl = domino.createDOMImplementation();
const document = domimpl.createHTMLDocument();
Object.assign(global, Element: domino.impl.Element, document};
}
// my-app code
You might be worried about domino entering client side code. To fix this, use separate bundles for server and client, wrap the above mocking code in a separate file and use the following in your my-app’s main file meant for the server bundle, an approach similar to how React ships production and development bundles - conditional imports.
Server main file
require(‘./globals-mocking’);
// my-app client code
Client main file
// my-app client code only
package’s main file
if (SERVER_ONLY) {
module.exports = require('./my-app-server.js');
} else {
module.exports = require('./my-app-client.js');
}
Use rollup's replace plugin and define SERVER_ONLY (https://github.com/rollup/rollup-plugin-replace#usage) for server entry only. If you use UglifyJS or simlilar tool that eliminates dead code, you wont have domino and duplicated server code.
EDIT: Noticed a minor issue. Condition should be if (SERVER_ONLY) {. Use the following definition along with it for the server entry file.
plugins: [
replace({
SERVER_ONLY: JSON.stringify(true)
})
]

Related

Requiring normal javascript file from node compiled script?

Firstly, i want to state i'm very new to anything to do with node...
Before i state my issue, here is some example code to refer to.
test.js
//test.js
const myMessage = 'Bananas';
export default myMessage; // Im not to sure about this line (problem)
main.js
//main.js
const test = require('./test.js');
console.log(test.myMessage);
I want to require a normal external javascript script called test.js from a node compiled script called main.js. I have compiled main.js simply by typing node main.js in my terminal. But node spat out an error 'Unexpected token export'. I know I'm doing something wrong here. Do i use "Modules"? How do i exclude the export statement?
Thanks for reading, sorry if my problem is making people facepalm on how dumb this issue might seem.
I think the external file you are trying to require is esModule. Such files can't be directly required unless you transpile them to commonJSModule. You have two solutions.
Transpile esModule to commonJSModule using babel. (or change export to module.exports in your test.js)
If you use new node version you can change it's extension to .mjs (rename test.js to test.mjs)
Please take a look at this Medium article which should help.
https://medium.com/#giltayar/native-es-modules-in-nodejs-status-and-future-directions-part-i-ee5ea3001f71
The export syntax is not yet supported in Nodejs (its in an alpha version), instead Nodejs provides a global object* (module.exports) which is what you get back with the require() call, so you just have to set that objects property to the thing you want to export:
const myMessage = 'Bananas';
module.exports.myMessage = myMessage;
or shorter:
exports.myMessage = 'Bananas';
*global in the sense of "it exists although you haven't defined it", actually for every script that gets executed, a new module object will be created that can only be accessed inside of that script.
Use babel register: https://babeljs.io/docs/en/babel-register
npm install #babel/core #babel/register #babel/preset-env --save
And require it in your main.js:
require('#babel/register')({
presets: [
[
'#babel/preset-env',
{
targets: {
node: true
}
}
]
],
ignore: [
/node_modules/
]
});
This will parse other required files through babel which are not in node_modules, so ES6 import/export will work, and it will also polyfill features not present in your current version of node (node: true).
Note this should only be used if you have to require front-end scripts you can't reasonably modify. It's heavyweight to parse every require so if you do have to, make ignore as strict as possible (or even better use the only option) so you're only parsing what you need.

Is there a way to cause the JS engine to load a .js file without explicitly importing something from it?

Maybe I'm trying to do something silly, but I've got a web application (Angular2+), and I'm trying to build it in an extensible/modular way. In particular, I've got various, well, modules for lack of a better term, that I'd like to be able to include or not, depending on what kind of deployment is desired. These modules include various functionality that is implemented via extending base classes.
To simplify things, imagine there is a GenericModuleDefinition class, and there are two modules - ModuleOne.js and ModuleTwo.js. The first defines a ModuleOneDefinitionClass and instantiate an exported instance ModuleOneDefinition, and then registers it with the ModuleRegistry. The second module does an analogous thing.
(To be clear - it registers the ModuleXXXDefinition object with the ModuleRegistry when the ModuleXXX.js file is run (e.g. because of some other .js file imports one of its exports). If it is not run, then clearly nothing gets registered - and this is the problem I'm having, as I describe below.)
The ModuleRegistry has some methods that will iterate over all the Modules and call their individual methods. In this example, there might be a method called ModuleRegistry.initAllModules(), which then calls the initModule() method on each of the registered Modules.
At startup, my application (say, in index.js) calls ModuleRegistry.initAllModules(). Obviously, because index.js imports the exported ModuleRegistry symbol, this will cause the ModuleRegistry.js code to get pulled in, but since none of the exports from either of the two Module .js files is explicitly referenced, these files will not have been pulled in, and so the ModuleOneDefinition and ModuleTwoDefinition objects will not have been instantiated and registered with the ModuleRegistry - so the call to initAllModules() will be for naught.
Obviously, I could just put meaningless references to each of these ModuleDefinition objects in my index.js, which would force them to be pulled in, so that they were registered by the time I call initAllModules(). But this requires changes to the index.js file depending on whether I want to deploy it with ModuleTwo or without. I was hoping to have the mere existence of the ModuleTwo.js be enough to cause the file to get pulled in and the resulting ModuleTwoDefinition to get registered with the ModuleRegistry.
Is there a standard way to handle this kind of situation? Am I stuck having to edit some global file (either index.js or some other file it references) so that it has information about all the included Modules so that it can then go and load them? Or is there a clever way to cause JavaScript to execute all the .js files in a directory so that merely copying the files it would be enough to get them to load at startup?
a clever way to cause xxJavaScriptxx Node.js to execute all the .js files in a directory:
var fs = require('fs') // node filesystem
var path = require('path') // node path
function hasJsExtension(item) {
return item != 'index.js' && path.extname(item) === '.js'
}
function pathHere(item) {
return path.join('.', item)
}
fs.readdir('./', function(err, list) {
if (err) return err
list.filter(hasJsExtension).map(pathHere).forEach(require) // require them all
})
Angular is pretty different, all the more if it is ng serve who checks if your app needs a module, and if so serves the corresponding js file, at any time needed, not at first load time.
In fact your situation reminds me of C++ with header files Declaration and cpp files with implementation, maybe you just need a defineAllModules function before initAllModules.
Another way could be considering finding out how to exclude those modules from ng-serve, and include them as scripts in your HTML before the others, they would so be defined (if present and so, served), and called by angular if necesary, the only cavehat is the error in the console if one script tag is not fetched, but your app will work anyway, if it supposed to do so.
But anyway, it would be declaring/defining those modules somewhere in ng-serve and also in the HTML.
In your own special case, and not willing to under-evalute ng-serve, but is the total js for your app too heavy to be served at once? (minified and all the ...), since the good-to-go solution may be one of the many tools to build and rebuild your production all.js from your dev js folder at will, or like you said, with a drag&drop in your folder.
Such tool is, again, server-side, but even if you only can push/FTP your javascript, you could use it in your prefered dev environment and just push your new version. To see a list of such tools google 'YourDevEnvironment bundle javascript'.
To do more with angular serve and append static js files under specific conditions, you should use webpack so the first option i see here is eject your webpack configuration and after that you can specify what angular should load or not.
With that said, i will give an example:
With angular cli and ng serve any external javascript files you wanna include, you have to put them inside the scripts array in the angular-cli.json file.However you can not control which file should be included and which one not.
By using webpack configuration you can specify all these thing by passing a flag from your terminal to the webpack config file and do all the process right there.
Example:
var env.commandLineParamater, plugins;
if(env.commandLineParamater == 'production'){
plugins = [
new ScriptsWebpackPlugin({
"name": "scripts",
"sourceMap": true,
"filename": "scripts.bundle.js",
"scripts": [
"D:\\Tutorial\\Angular\\demo-project\\node_moduels\\bootstrap\\dist\\bootstrap.min.js",
"D:\\Tutorial\\Angular\\demo-project\\node_moduels\\jquery\\dist\\jquery.min.js"
],
"basePath": "D:\\Tutorial\\Angular\\demo-project"
}),
]}else{
plugins = [
new ScriptsWebpackPlugin({
"name": "scripts",
"sourceMap": true,
"filename": "scripts.bundle.js",
"scripts": [
"D:\\Tutorial\\Angular\\demo-project\\node_moduels\\bootstrap\\dist\\bootstrap.min.js"
],
"basePath": "D:\\Tutorial\\Angular\\demo-project"
}),
]
}
then:
module.exports = (env) => {
"plugins": plugins,
// other webpack configuration
}
The script.js bundle will be loaded before your main app bundle and so you can control what you load when you run npm run start instead of ng-serve.
To Eject your webpack configuration, use ng eject.
Generally speaking, when you need to control some of angular ng-serve working, you should extract your own webpack config and customize it as you want.

Fullstack module reuse with webpack

I'm working on a project that uses typescript on both backend (nodejs) and front-end. The project has some cryptography involved which means I'm using WebCrypto - on the backend I use node-webcrypto-ossl as a shim so I may share code between the client and the server.
Is there a way to make a module export one thing on the client and another on the server so I may just do import * as crypto from './webcrypto' to expose a the interface in a common manner?
On the server it should export node-webcrypto-ossl and on the client just expose window.crypto.
I tried various things but webpack keeps attempting to pull node-webcrypto-ossl into the browser which unsurprisingly fails.
Here's my (failed) attempt:
let crypto = null;
if (typeof window === 'undefined') {
const WebCrypto = require('node-webcrypto-ossl');
crypto = new WebCrypto();
} else {
crypto = window.crypto;
}
export {
crypto as webcrypto
};
Approach itself is mostly correct, you may need configure webpack's externals to keep node-webcrypto-ossl to be loaded in nodejs context only. Shortest pseudo looks like
webpack.config.js
...
externals: {
'node-webcrypto-ossl: {
commonjs: 'node-webcrypto-ossl'
},
then webpack will not try to bundle specific module but leave require as is for those modules.
In addition to those, you may able to configure definePlugin as well for node.js / browser context so your crypto module can be statically compiled for each environment in build time, instead of looking object in runtime.

How can I eliminate a server-only dependency when porting code to browser using webpack?

I have a TypeScript code that uses jsdom on the server side (node.js) to generate a DOM structure:
DomCreator.ts
const {JSDOM} = require('jsdom');
To run this code in the browser, I configured Webpack to treat jsdom as an external and not include it in the bundle:
webpack.config.js
externals: {
jsdom: "jsdom"
}
When the code is executed in the browser, a real DOM exists so jsdom is of no real use. However, webpack still expects jsdom to be separately available in the global space, due to the way externals work.
My workaround was to create a fake jsdom object on top of the page, so I don't run into a jsdom is not defined error in browser:
index.html
<script type="text/javascript">
var jsdom = {} ;
jsdom.JSDOM = 0 ;
</script>
I have put condition in TypeScript code to check the presence of window object (as a proxy to being in browser environment) and create the DOM object either through jsdom or use native browser document based on this condition:
DomCreator.ts
if(typeof window != 'undefined'){
console.log('browser detected.');
body = document.getElementsByTagName('body');
}
else {
console.log('node detected');
body = (new JSDOM()).dom.window.document.querySelector("body") ;
}
This works OK but looks patchy to me. Are there best practices for handling situations like this? In particular, I don't want to create the fake jsdom object in the client-side code, and also I'm not sure if checking window is the best way of separating the server and client side logic.

Using browserify with typescript to include a node module

I'm attempting to use a node module in a typescript application and I've included it in my TypeScript code via require().
I build my typescript to JS and all works well if I run it through node which is great.
I'm looking for a way to use the same code and run it through the browser. Browserify seemed like a sensible solution.
I popped a post build action on my VS project and I'm building a 206KB file via browserify (so it's certainly doing something!). Unfortunately my tiny bit of Typescript doesn't seem to be accessible when it's been browserified.
I'm not that familiar with what browserify should be generating so not quite sure whether what it's wrapped my .js code in is correct (can post snippets if it helps).
So my question is twofold really, I'm looking for the answer to either:
Is there a recommended way to write TypeScript post 0.9 to allow it to be run after it's been browserified?
Is there a way to simple tell TypeScript to pull in the 'require' dependency on its own?
Any thoughts or info in this area would be greatly appreciated.
EDIT:
Just to clarify, I'm generating my .js from the .ts during save/build, and in a post build action, pointing browserify to the output. An abridged js output looks like this:
var TestModule;
(function (TestModule) {
function launchDrone() {
var exports = require('ar-drone');
var client = exports.createClient();
}
})(TestModule || (TestModule = {}));
When I generate the browserified file from that, I can't access TestModule or launchDrone in any context (or certainly not from window. ) is there some trick to accessing the browserified code?
It looks like you potentially are not exporting TestModule? Your TestModule file should look like this:
module TestModule {
export function launchDrone() {
var exports = require('ar-drone');
var client = exports.createClient();
}
}
export = TestModule;
This way you should be able to launch TestModule and TestModule.launchDrone from the window.

Categories