Webpack and Nodejs isomorphic require with absolute path - javascript

GOAL: I am trying to set up a project in nodejs and webpack such that the require function can use the project directory as root, so I can require with absolute path relative to project root in both environments (isomorphic uses i.e. React server+client render).
SITUATION: In webpack you can set the config.resolve.root to make it work, but in nodejs, its best practice not to override/modify the global.require.
PROPOSITION 1: I can make a new global function
global.p_require
so it works in node; however, I cannot find a way to let webpack parse "p_require" into __webpack_require__ without changing the webpack source code.
PROPOSITION 2: I can make a new global variable
global.ROOT_DIR = process.cwd()
so it works in node by
require(ROOT_DIR + <relative path to root>);
however, webpack would recognize this as dynamic require. Is there a way such that webpack would parse ROOT_DIR? I have already tried the Define Plugin, but it seems to load after require is parsed by webpack.
QUESTION
Anyone has a solution or faces the same issue?

I'm addressing this by letting webpack do more; instead of "node and webpack", it's "webpack: client and server". I have webpack do a build for the client and a build for the server (the latter uses 'node' as its target property in config). It's easy to customize the directories webpack uses to require, so you let it do its work and create a build for node.
When rendering on the server, you just require the compiled server build. If you need to pass some stuff in from the server to the application that webpack built, wire that up in the entry point that you use for the server build -- webpack will build it as a commonJs module, so your entry point can export whatever is the most convenient interface when the server needs to render.

Related

Cannot find module an external js whith fullpath after build a bundle whith Webpack and Babel in NodeJS

I build a bundle with all the needed resources with Webpack including node_modules because Im going to run this bundle in another place where the package.json and node_modules not exist, thats the reason why Im building the bundle including node_modules.
In some moment, the bundle needs to require an external js that is downloaded with a dynamic name, if I move the bundle to the final location and I run it with Node, when it try to require('dynamic_fullpath.js'), the log tells Error: Cannot find module dynamic_fullpath.js, the file(in this case: dynamic_fullpath.js) exists in the right path.
I think the problem is: Webpack changes the require js methods to require webpack methods and when builds the bundle: the dynamic_fullpath.js does not exist and dont add to the bundle.
Any idea how to resolve this dynamic require?
Finally the problem as supposed: Webpack changes the require js methods to require webpack methods and when builds the bundle: the dynamic_fullpath.js does not exist and dont add to the bundle, so, to avoid that on an specific require I found this solution, and as the post said:
"
A simpler way to pull this off without resorting to eval is:
const requireFunc = typeof __webpack_require__ === "function" ? __non_webpack_require__ : require;
const foo = requireFunc(moduleName);
In the bundled output, this will become
const requireFunc = true ? require : require;
const foo = requireFunc(moduleName);
"
The solution was found here

Webpack require npm module dynamiclly by variable

I can't find a way to require NPM module dynamically by variable.
Here is a sample code what I'm trying to do, Everything works great expect import NPM module dynamically.
const firstModule = 'my-npm-module';
const secondModule = './MyReactComponent';
// NPM Module
import(firstModule).then(...); // Doesn't work
import('my-npm-module').then(...); // Works
// Local React Component
import(secondModule).then(...); // Works
import('./MyReactComponent').then(...); // Works
From the Webpack docs on dynamic import:
Fully dynamic statements, such as import(foo), will fail because
webpack requires at least some file location information. This is
because foo could potentially be any path to any file in your system
or project. The import() must contain at least some information about
where the module is located, so bundling can be limited to a specific
directory or set of files.
Your best option would probably be to either not use dynamic loading for anything in node_modules, or add the explicit path to the module, e.g.
import(`./node_modules/${firstModule}/index.js`);

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.

Setting environment configuration in a TypeScript app with Webpack

I have gone through several solutions including the ones listed here:
Environment Variables in an isomorphic JS app: Webpack find & replace?
Passing environment-dependent variables in webpack
I am used to using something like gulp-replace-task to find and update a config file for the app to replace things like ##SERVER_URL with something set from the environment.
That way I can do export SERVER_URL=something or run the script with SERVER_URL=something gulp build to set the configuration.
I've tried all of the following:
Using the transform-loader plus envify
This is a suggestion from the first question, but it does not work for me because of:
Module build failed: Error: Parse Error: Line 1: Illegal import declaration
at throwError (ngapp/node_modules/esprima-fb/esprima.js:2823:21)
Seems like esprima-fb is using an import declaration that Webpack can't use for some reason or another. The project is no longer maintained either, so this may be the wrong road to go down.
Using DefinePlugin
I've added:
module: {plugins: [new webpack.DefinePlugin({"process.env.SERVER_URL": "something"})]}
This seems to be ignored, or at least process.env.SERVER_URL does not get interpolated in my typescript files. When I console.log(process.env), it emits an empty object.
Setting using --define for webpack
I updated my npm script:
"start": "webpack-dev-server --define process.env.SERVER_URL=${SERVER_URL}"
However this just ends up replacing process.env.SERVER_URL in my code with a literal "${SERVER_URL}" rather than being interpolated in the npm script.
Is there a simple / convenient (or at this point really any) way to use environment variables in TypeScript apps built with Webpack?
My Webpack setup is essentially what is listed in the Angular docs.

Make pdf.js 1.5 and require.js play nice together

In my project I have long used require.js together with the pdf.js library. Pdf.js have until recently been putting itself on the global object. I could still use it in my requirejs config by using a shim. The pdfjs library will in turn load another library called pdf.worker. In order to find this module the solution was to add a property to the global PDFJS object called workerSrc and point to the file on disk. This could be done before or after loading the pdfjs library.
The pdfjs library uses the pdf.worker to start a WebWorker and to do so it needs the path to a source file.
When I tried to update the pdfjs library in my project to a new version (1.5.314) the way to load and include the library have changed to use UMD modules and now everything get's a bit tricky.
The pdfjs library checks if the environment is using requirejs and so it defines itself as a module named "pdfjs-dist/build/pdf". When this module loads it checks for a module named "pdfjs-dist/build/pdf.worker". Since I have another folder structure I have added them to my requirejs config object with a new path:
paths: {
"pdfjs-dist/build/pdf": "vendor/pdfjs/build/pdf",
"pdfjs-dist/build/pdf.worker": "vendor/pdfjs/build/pdf.worker"
}
This is to make the module loader to find the modules at all. In development this works great. When I try to use the requirejs optimizer in my grunt build step however, it will put all of my project files into one single file. This step will try to include the pdf.worker module as well and this generates an error:
Error: Cannot uglify2 file: vendor/pdfjs/build/pdf.worker.js. Skipping
it. Error is: RangeError: Maximum call stack size exceeded
Since the worker source needs to be in a single file on disk I don't want this module to be included.
So I've tried two different config-settings in the requirejs config.
The first attempt was to override the paths property in my grunt build options:
paths: {
"pdfjs-dist/build/pdf.worker": "empty:"
}
The second thing to test is to exclude it from my module:
modules: [{
name: "core/app",
exclude: [
"pdfjs-dist/build/pdf.worker"
]
}]
Both techniques should tell the optimizer not to include the module but both attempts ended up with the same error as before. The requirejs optimizer still tries to include the module into the build and the attempt to uglify it ends up with a RangeError.
One could argue that since the uglify step fails it will not be included and I can go about my bussiness, but if the uglify step should happen to start working at a new update of pdfjs - what then?
Can anyone help me figure out why the requirejs config won't just exclude it in the build step and how to make it do so.
I found out what the core of my problem was and now I have a way to solve the problem and make my build process to work. My build step in grunt is using grunt-contrib-requirejs and I needed to override some options in the config for this job.
I didn't want the pdf.worker module to be included in my concatenated and minified production code.
I didn't want r.js to minify it only to later exclude it from the concatenated file.
I tried to solve the first problem thinking that it would mean that the second problem also should be solved. When I figured out the two were separate I finally found a solution.
In the r.js example on github there is a property named fileExclusionRegExp. This is what I now use to tell r.js not to copy the file over to the build folder.
fileExclusionRegExp: /pdf.worker.js/
Second, I need to tell the optimizer to not include this module in the concatenated file. This is done by overriding the paths property for this module to the value of "empty:".
paths: {
"pdfjs-dist/build/pdf.worker": "empty:"
}
Now my grunt build step will work without errors and all is well.
Thanks to async5 for informing me about the bug with uglify and the pdf.worker. The workaround is applied in another grunt task that uglify the worker and copies it into the build-folder separately. The options object for the grunt-contrib-uglify task will need this property in order to not break the pdf.worker file:
compress: {
sequences: false
}
Now my project works great when built for production.

Categories