How to include manual import() in Webpack BundleHow do JavaScript closures work?How do I check if an element is hidden in jQuery?How do I remove a property from a JavaScript object?How do I check if an array includes an object in JavaScript?How do I redirect to another webpage?How do I include a JavaScript file in another JavaScript file?How to check whether a string contains a substring in JavaScript?How do I remove a particular element from an array in JavaScript?Managing jQuery plugin dependency in webpackNPM vs. Bower vs. Browserify vs. Gulp vs. Grunt vs. Webpack
Why is it a bad idea to hire a hitman to eliminate most corrupt politicians?
How to travel to Japan while expressing milk?
Does Dispel Magic work on Tiny Hut?
Do Iron Man suits sport waste management systems?
What's the meaning of "Sollensaussagen"?
Finding the reason behind the value of the integral.
How can I deal with my CEO asking me to hire someone with a higher salary than me, a co-founder?
Why didn't Boeing produce its own regional jet?
How to enclose theorems and definition in rectangles?
How do conventional missiles fly?
Can a virus destroy the BIOS of a modern computer?
Avoiding the "not like other girls" trope?
Does int main() need a declaration on C++?
Knowledge-based authentication using Domain-driven Design in C#
Is this draw by repetition?
Why was Sir Cadogan fired?
How to compactly explain secondary and tertiary characters without resorting to stereotypes?
How obscure is the use of 令 in 令和?
files created then deleted at every second in tmp directory
Bullying boss launched a smear campaign and made me unemployable
Different meanings of こわい
Is it a bad idea to plug the other end of ESD strap to wall ground?
How to install cross-compiler on Ubuntu 18.04?
How do I exit BASH while loop using modulus operator?
How to include manual import() in Webpack Bundle
How do JavaScript closures work?How do I check if an element is hidden in jQuery?How do I remove a property from a JavaScript object?How do I check if an array includes an object in JavaScript?How do I redirect to another webpage?How do I include a JavaScript file in another JavaScript file?How to check whether a string contains a substring in JavaScript?How do I remove a particular element from an array in JavaScript?Managing jQuery plugin dependency in webpackNPM vs. Bower vs. Browserify vs. Gulp vs. Grunt vs. Webpack
I am quite new to Webpack, so bear with me if thats a stupid question.
My goal is to transform my old, AMD based codebase to a ES6 Module based solution. What I am struggling with is handling dynamic import()
s. So my app router works on a module basis, i.e. each route is mapped to a module path and then require
d. Since I know what modules will be included, I just add those dynamically imported modules to my r.js configuration and am able to build everything in a single file, with all require calls still working.
Now, I am trying to do the same with ES6 modules and Webpack. With my devmode this is no problem as I can just replace require()
with import()
. However I cannot get this to work with bundling. Either Webpack splits my code (and still fails to load the dynamic module anyways), or - if I use the Array format for the entry
config, the dynamic module is included in the bundle but loading still fails: Error: Cannot find module '/src/app/DynClass.js'
This is how my Webpack config looks like:
const webpack = require('webpack');
const path = require('path');
module.exports =
mode: "development",
entry: ['./main.js', './app/DynClass.js'],
output:
filename: 'main.js',
path: path.resolve(__dirname, "../client/")
,
resolve:
alias:
"/src": path.resolve(__dirname, '')
,
module:
rules: [
test: /.tpl$/i,
use: 'raw-loader',
,
]
;
So basically I want to tell Webpack: "hey, there is another module (or more) that is to be loaded dynamically and I want it to be included in the bundle"
How can I do this?
javascript webpack es6-modules
add a comment |
I am quite new to Webpack, so bear with me if thats a stupid question.
My goal is to transform my old, AMD based codebase to a ES6 Module based solution. What I am struggling with is handling dynamic import()
s. So my app router works on a module basis, i.e. each route is mapped to a module path and then require
d. Since I know what modules will be included, I just add those dynamically imported modules to my r.js configuration and am able to build everything in a single file, with all require calls still working.
Now, I am trying to do the same with ES6 modules and Webpack. With my devmode this is no problem as I can just replace require()
with import()
. However I cannot get this to work with bundling. Either Webpack splits my code (and still fails to load the dynamic module anyways), or - if I use the Array format for the entry
config, the dynamic module is included in the bundle but loading still fails: Error: Cannot find module '/src/app/DynClass.js'
This is how my Webpack config looks like:
const webpack = require('webpack');
const path = require('path');
module.exports =
mode: "development",
entry: ['./main.js', './app/DynClass.js'],
output:
filename: 'main.js',
path: path.resolve(__dirname, "../client/")
,
resolve:
alias:
"/src": path.resolve(__dirname, '')
,
module:
rules: [
test: /.tpl$/i,
use: 'raw-loader',
,
]
;
So basically I want to tell Webpack: "hey, there is another module (or more) that is to be loaded dynamically and I want it to be included in the bundle"
How can I do this?
javascript webpack es6-modules
Have you looked at webpack's explanation of the subject?
– flup
Mar 13 at 22:27
2
there are many unclear things here, how do you useimport()
in your code? also where is/src/app/DynClass.js
used?
– d7my
Mar 14 at 12:01
@flup: yes I have. @d7myimport()
is used like this:const module = await import(modulePath);
, so I cannot use a template here.DynClass
is just a test-dummy being imported by the code I just mentioned. So there will be a kind of configuration object so my app-router knows what routes to match and what module to import.
– frontend_dev
Mar 14 at 12:40
add a comment |
I am quite new to Webpack, so bear with me if thats a stupid question.
My goal is to transform my old, AMD based codebase to a ES6 Module based solution. What I am struggling with is handling dynamic import()
s. So my app router works on a module basis, i.e. each route is mapped to a module path and then require
d. Since I know what modules will be included, I just add those dynamically imported modules to my r.js configuration and am able to build everything in a single file, with all require calls still working.
Now, I am trying to do the same with ES6 modules and Webpack. With my devmode this is no problem as I can just replace require()
with import()
. However I cannot get this to work with bundling. Either Webpack splits my code (and still fails to load the dynamic module anyways), or - if I use the Array format for the entry
config, the dynamic module is included in the bundle but loading still fails: Error: Cannot find module '/src/app/DynClass.js'
This is how my Webpack config looks like:
const webpack = require('webpack');
const path = require('path');
module.exports =
mode: "development",
entry: ['./main.js', './app/DynClass.js'],
output:
filename: 'main.js',
path: path.resolve(__dirname, "../client/")
,
resolve:
alias:
"/src": path.resolve(__dirname, '')
,
module:
rules: [
test: /.tpl$/i,
use: 'raw-loader',
,
]
;
So basically I want to tell Webpack: "hey, there is another module (or more) that is to be loaded dynamically and I want it to be included in the bundle"
How can I do this?
javascript webpack es6-modules
I am quite new to Webpack, so bear with me if thats a stupid question.
My goal is to transform my old, AMD based codebase to a ES6 Module based solution. What I am struggling with is handling dynamic import()
s. So my app router works on a module basis, i.e. each route is mapped to a module path and then require
d. Since I know what modules will be included, I just add those dynamically imported modules to my r.js configuration and am able to build everything in a single file, with all require calls still working.
Now, I am trying to do the same with ES6 modules and Webpack. With my devmode this is no problem as I can just replace require()
with import()
. However I cannot get this to work with bundling. Either Webpack splits my code (and still fails to load the dynamic module anyways), or - if I use the Array format for the entry
config, the dynamic module is included in the bundle but loading still fails: Error: Cannot find module '/src/app/DynClass.js'
This is how my Webpack config looks like:
const webpack = require('webpack');
const path = require('path');
module.exports =
mode: "development",
entry: ['./main.js', './app/DynClass.js'],
output:
filename: 'main.js',
path: path.resolve(__dirname, "../client/")
,
resolve:
alias:
"/src": path.resolve(__dirname, '')
,
module:
rules: [
test: /.tpl$/i,
use: 'raw-loader',
,
]
;
So basically I want to tell Webpack: "hey, there is another module (or more) that is to be loaded dynamically and I want it to be included in the bundle"
How can I do this?
javascript webpack es6-modules
javascript webpack es6-modules
edited Mar 16 at 16:30
frontend_dev
asked Mar 7 at 20:54
frontend_devfrontend_dev
1,1551121
1,1551121
Have you looked at webpack's explanation of the subject?
– flup
Mar 13 at 22:27
2
there are many unclear things here, how do you useimport()
in your code? also where is/src/app/DynClass.js
used?
– d7my
Mar 14 at 12:01
@flup: yes I have. @d7myimport()
is used like this:const module = await import(modulePath);
, so I cannot use a template here.DynClass
is just a test-dummy being imported by the code I just mentioned. So there will be a kind of configuration object so my app-router knows what routes to match and what module to import.
– frontend_dev
Mar 14 at 12:40
add a comment |
Have you looked at webpack's explanation of the subject?
– flup
Mar 13 at 22:27
2
there are many unclear things here, how do you useimport()
in your code? also where is/src/app/DynClass.js
used?
– d7my
Mar 14 at 12:01
@flup: yes I have. @d7myimport()
is used like this:const module = await import(modulePath);
, so I cannot use a template here.DynClass
is just a test-dummy being imported by the code I just mentioned. So there will be a kind of configuration object so my app-router knows what routes to match and what module to import.
– frontend_dev
Mar 14 at 12:40
Have you looked at webpack's explanation of the subject?
– flup
Mar 13 at 22:27
Have you looked at webpack's explanation of the subject?
– flup
Mar 13 at 22:27
2
2
there are many unclear things here, how do you use
import()
in your code? also where is /src/app/DynClass.js
used?– d7my
Mar 14 at 12:01
there are many unclear things here, how do you use
import()
in your code? also where is /src/app/DynClass.js
used?– d7my
Mar 14 at 12:01
@flup: yes I have. @d7my
import()
is used like this: const module = await import(modulePath);
, so I cannot use a template here. DynClass
is just a test-dummy being imported by the code I just mentioned. So there will be a kind of configuration object so my app-router knows what routes to match and what module to import.– frontend_dev
Mar 14 at 12:40
@flup: yes I have. @d7my
import()
is used like this: const module = await import(modulePath);
, so I cannot use a template here. DynClass
is just a test-dummy being imported by the code I just mentioned. So there will be a kind of configuration object so my app-router knows what routes to match and what module to import.– frontend_dev
Mar 14 at 12:40
add a comment |
1 Answer
1
active
oldest
votes
So yeah, after much fiddling there seems to be a light at the end of the tunnel. Still, this is not a 100% solution and it is surely not for the faint of heart, as it is quite ugly and fragile. But still I want to share my approach with you:
1) manual parsing of my routes config
My router uses a config file looking like this:
import StaticClass from "/src/app/StaticClass.js";
export default
StaticClass:
match: /^//,
module: StaticClass
,
DynClass:
match: /^//,
module: "/src/app/DynClass.js"
;
So as you can see the export is an object, with keys acting as the route id, and an object that contains the matches (regex based) and the module which should be executed by the router if the route matches. I can feed my router with both a Constructor function (or an object) for modules which are available immediatly (i.e. contained in the main chunk) or if the module value is a string, this means that the router has to load this module dynamically by using the path specified in the string.
So as I know what modules could be potentially loaded (but not if and when) I can now parse this file within my build process and transform the route config to something webpack can understand:
const path = require("path");
const fs = require("fs");
let routesSource = fs.readFileSync(path.resolve(__dirname, "app/routes.js"), "utf8");
routesSource = routesSource.substr(routesSource.indexOf("export default"));
routesSource = routesSource.replace(/module:s*((?!".*").)*$/gm, "module: undefined,");
routesSource = routesSource.replace(/r?n|r/g, "").replace("export default", "var routes = ");
eval(routesSource);
let dummySource = Object.entries(routes).reduce((acc, [routeName, routeConfig]) =>
if (typeof routeConfig.module === "string")
return acc + `import(/* webpackChunkName: "$routeName" */"$routeConfig.module");`;
return acc;
, "") + "export default ''";
(Yeah I know this is quite ugly and also a bit brittle so this surely could be done better)
Essentially I create a new, virtual module where every route entry which demands a dynamic import is translated, so:
DynClass:
match: /^//,
module: "/src/app/DynClass.js"
becomes:
import(/* webpackChunkName: "DynClass" */"/src/app/DynClass.js");
So the route id simply becomes the name of the chunk!
2) including the virtual module in the build
For this I use the virtual-module-webpack-plugin
:
plugins: [
new VirtualModulePlugin(
moduleName: "./app/dummy.js",
contents: dummySource
)
],
Where dummySource
is just a string containing the sourcecode of my virtual module I just have generated. Now, this module is pulled in and the "virtual imports" can be processed by webpack. But wait, I still need to import the dummy module, but I do not have any in my development mode (where I use everything natively, so no loaders).
So in my main code I do the following:
let isDev = false;
/** @remove */
isDev = true;
/** @endremove */
if (isDev) import('./app/dummy.js');
Where "dummy.js" is just an empty stub module while I am in development mode. The parts between that special comments are removed while building (using the webpack-loader-clean-pragma
loader), so while webpack "sees" the import for dummy.js
, this code will not be executed in the build itself since then isDev
evaluates to false
. And since we already defined a virtual module with the same path, the virtual module is included while building just like I want, and of course all dependencies are resolved as well.
3) Handling the actual loading
For development, this is quite easy:
import routes from './app/routes.js';
Object.entries(routes).forEach(async ([routeId, route]) =>
if (typeof route.module === "function")
new route.module;
else
const result = await import(route.module);
new result.default;
);
(Note that this is not the actual router code, just enough to help me with my PoC)
Well, but for the build I need something else, so I added some code specific to the build environment:
/** @remove */
const result = await import(route.module);
new result.default;
/** @endremove */
if (!isDev)
if (typeof route.module === "string") await __webpack_require__.e(routeId);
const result = __webpack_require__(route.module.replace("/src", "."));
new result.default;
Now, the loading code for the dev environment is just stripped out, and there is another loading code that uses webpack internally. I also check if the module value is a function or string, and if it is the latter I invoke the internal require.ensure
function to load the correct chunk: await __webpack_require__.e(routeId);
. Remember that I named my chunks when generating the virtual module? Now thats why I still can find them now!
4) more needs to be done
Another thing I encountered is when several dynamically loaded modules have the same dependencies, webpack tries to generate more chunks with names like module1~module2.bundle.js
, breaking my build. To counter this, I needed to make sure that all those shared modules go into a specific named bundle I called "shared":
optimization:
splitChunks:
chunks: "all",
name: "shared"
And when in production mode, I simply load this chunk manually before any dynamic modules depending on it are requested:
if (!isDev)
await __webpack_require__.e("shared");
Again, this code only runs in production mode!
Finally, I have to prevent webpack renaming my modules (and chunks) to something like "1", "2" etc, but rather keep the names I just have defined:
optimization:
namedChunks: true,
namedModules: true
Se yeah, there you have it! As I said this wasn't pretty but seems to work, at least with my simplified test setup. I really hope there aren't any blockers ahead of me when I do all the rest (like ESLint, SCSS etc)!
add a comment |
Your Answer
StackExchange.ifUsing("editor", function ()
StackExchange.using("externalEditor", function ()
StackExchange.using("snippets", function ()
StackExchange.snippets.init();
);
);
, "code-snippets");
StackExchange.ready(function()
var channelOptions =
tags: "".split(" "),
id: "1"
;
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function()
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled)
StackExchange.using("snippets", function()
createEditor();
);
else
createEditor();
);
function createEditor()
StackExchange.prepareEditor(
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: true,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: 10,
bindNavPrevention: true,
postfix: "",
imageUploader:
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
,
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
);
);
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f55052636%2fhow-to-include-manual-import-in-webpack-bundle%23new-answer', 'question_page');
);
Post as a guest
Required, but never shown
1 Answer
1
active
oldest
votes
1 Answer
1
active
oldest
votes
active
oldest
votes
active
oldest
votes
So yeah, after much fiddling there seems to be a light at the end of the tunnel. Still, this is not a 100% solution and it is surely not for the faint of heart, as it is quite ugly and fragile. But still I want to share my approach with you:
1) manual parsing of my routes config
My router uses a config file looking like this:
import StaticClass from "/src/app/StaticClass.js";
export default
StaticClass:
match: /^//,
module: StaticClass
,
DynClass:
match: /^//,
module: "/src/app/DynClass.js"
;
So as you can see the export is an object, with keys acting as the route id, and an object that contains the matches (regex based) and the module which should be executed by the router if the route matches. I can feed my router with both a Constructor function (or an object) for modules which are available immediatly (i.e. contained in the main chunk) or if the module value is a string, this means that the router has to load this module dynamically by using the path specified in the string.
So as I know what modules could be potentially loaded (but not if and when) I can now parse this file within my build process and transform the route config to something webpack can understand:
const path = require("path");
const fs = require("fs");
let routesSource = fs.readFileSync(path.resolve(__dirname, "app/routes.js"), "utf8");
routesSource = routesSource.substr(routesSource.indexOf("export default"));
routesSource = routesSource.replace(/module:s*((?!".*").)*$/gm, "module: undefined,");
routesSource = routesSource.replace(/r?n|r/g, "").replace("export default", "var routes = ");
eval(routesSource);
let dummySource = Object.entries(routes).reduce((acc, [routeName, routeConfig]) =>
if (typeof routeConfig.module === "string")
return acc + `import(/* webpackChunkName: "$routeName" */"$routeConfig.module");`;
return acc;
, "") + "export default ''";
(Yeah I know this is quite ugly and also a bit brittle so this surely could be done better)
Essentially I create a new, virtual module where every route entry which demands a dynamic import is translated, so:
DynClass:
match: /^//,
module: "/src/app/DynClass.js"
becomes:
import(/* webpackChunkName: "DynClass" */"/src/app/DynClass.js");
So the route id simply becomes the name of the chunk!
2) including the virtual module in the build
For this I use the virtual-module-webpack-plugin
:
plugins: [
new VirtualModulePlugin(
moduleName: "./app/dummy.js",
contents: dummySource
)
],
Where dummySource
is just a string containing the sourcecode of my virtual module I just have generated. Now, this module is pulled in and the "virtual imports" can be processed by webpack. But wait, I still need to import the dummy module, but I do not have any in my development mode (where I use everything natively, so no loaders).
So in my main code I do the following:
let isDev = false;
/** @remove */
isDev = true;
/** @endremove */
if (isDev) import('./app/dummy.js');
Where "dummy.js" is just an empty stub module while I am in development mode. The parts between that special comments are removed while building (using the webpack-loader-clean-pragma
loader), so while webpack "sees" the import for dummy.js
, this code will not be executed in the build itself since then isDev
evaluates to false
. And since we already defined a virtual module with the same path, the virtual module is included while building just like I want, and of course all dependencies are resolved as well.
3) Handling the actual loading
For development, this is quite easy:
import routes from './app/routes.js';
Object.entries(routes).forEach(async ([routeId, route]) =>
if (typeof route.module === "function")
new route.module;
else
const result = await import(route.module);
new result.default;
);
(Note that this is not the actual router code, just enough to help me with my PoC)
Well, but for the build I need something else, so I added some code specific to the build environment:
/** @remove */
const result = await import(route.module);
new result.default;
/** @endremove */
if (!isDev)
if (typeof route.module === "string") await __webpack_require__.e(routeId);
const result = __webpack_require__(route.module.replace("/src", "."));
new result.default;
Now, the loading code for the dev environment is just stripped out, and there is another loading code that uses webpack internally. I also check if the module value is a function or string, and if it is the latter I invoke the internal require.ensure
function to load the correct chunk: await __webpack_require__.e(routeId);
. Remember that I named my chunks when generating the virtual module? Now thats why I still can find them now!
4) more needs to be done
Another thing I encountered is when several dynamically loaded modules have the same dependencies, webpack tries to generate more chunks with names like module1~module2.bundle.js
, breaking my build. To counter this, I needed to make sure that all those shared modules go into a specific named bundle I called "shared":
optimization:
splitChunks:
chunks: "all",
name: "shared"
And when in production mode, I simply load this chunk manually before any dynamic modules depending on it are requested:
if (!isDev)
await __webpack_require__.e("shared");
Again, this code only runs in production mode!
Finally, I have to prevent webpack renaming my modules (and chunks) to something like "1", "2" etc, but rather keep the names I just have defined:
optimization:
namedChunks: true,
namedModules: true
Se yeah, there you have it! As I said this wasn't pretty but seems to work, at least with my simplified test setup. I really hope there aren't any blockers ahead of me when I do all the rest (like ESLint, SCSS etc)!
add a comment |
So yeah, after much fiddling there seems to be a light at the end of the tunnel. Still, this is not a 100% solution and it is surely not for the faint of heart, as it is quite ugly and fragile. But still I want to share my approach with you:
1) manual parsing of my routes config
My router uses a config file looking like this:
import StaticClass from "/src/app/StaticClass.js";
export default
StaticClass:
match: /^//,
module: StaticClass
,
DynClass:
match: /^//,
module: "/src/app/DynClass.js"
;
So as you can see the export is an object, with keys acting as the route id, and an object that contains the matches (regex based) and the module which should be executed by the router if the route matches. I can feed my router with both a Constructor function (or an object) for modules which are available immediatly (i.e. contained in the main chunk) or if the module value is a string, this means that the router has to load this module dynamically by using the path specified in the string.
So as I know what modules could be potentially loaded (but not if and when) I can now parse this file within my build process and transform the route config to something webpack can understand:
const path = require("path");
const fs = require("fs");
let routesSource = fs.readFileSync(path.resolve(__dirname, "app/routes.js"), "utf8");
routesSource = routesSource.substr(routesSource.indexOf("export default"));
routesSource = routesSource.replace(/module:s*((?!".*").)*$/gm, "module: undefined,");
routesSource = routesSource.replace(/r?n|r/g, "").replace("export default", "var routes = ");
eval(routesSource);
let dummySource = Object.entries(routes).reduce((acc, [routeName, routeConfig]) =>
if (typeof routeConfig.module === "string")
return acc + `import(/* webpackChunkName: "$routeName" */"$routeConfig.module");`;
return acc;
, "") + "export default ''";
(Yeah I know this is quite ugly and also a bit brittle so this surely could be done better)
Essentially I create a new, virtual module where every route entry which demands a dynamic import is translated, so:
DynClass:
match: /^//,
module: "/src/app/DynClass.js"
becomes:
import(/* webpackChunkName: "DynClass" */"/src/app/DynClass.js");
So the route id simply becomes the name of the chunk!
2) including the virtual module in the build
For this I use the virtual-module-webpack-plugin
:
plugins: [
new VirtualModulePlugin(
moduleName: "./app/dummy.js",
contents: dummySource
)
],
Where dummySource
is just a string containing the sourcecode of my virtual module I just have generated. Now, this module is pulled in and the "virtual imports" can be processed by webpack. But wait, I still need to import the dummy module, but I do not have any in my development mode (where I use everything natively, so no loaders).
So in my main code I do the following:
let isDev = false;
/** @remove */
isDev = true;
/** @endremove */
if (isDev) import('./app/dummy.js');
Where "dummy.js" is just an empty stub module while I am in development mode. The parts between that special comments are removed while building (using the webpack-loader-clean-pragma
loader), so while webpack "sees" the import for dummy.js
, this code will not be executed in the build itself since then isDev
evaluates to false
. And since we already defined a virtual module with the same path, the virtual module is included while building just like I want, and of course all dependencies are resolved as well.
3) Handling the actual loading
For development, this is quite easy:
import routes from './app/routes.js';
Object.entries(routes).forEach(async ([routeId, route]) =>
if (typeof route.module === "function")
new route.module;
else
const result = await import(route.module);
new result.default;
);
(Note that this is not the actual router code, just enough to help me with my PoC)
Well, but for the build I need something else, so I added some code specific to the build environment:
/** @remove */
const result = await import(route.module);
new result.default;
/** @endremove */
if (!isDev)
if (typeof route.module === "string") await __webpack_require__.e(routeId);
const result = __webpack_require__(route.module.replace("/src", "."));
new result.default;
Now, the loading code for the dev environment is just stripped out, and there is another loading code that uses webpack internally. I also check if the module value is a function or string, and if it is the latter I invoke the internal require.ensure
function to load the correct chunk: await __webpack_require__.e(routeId);
. Remember that I named my chunks when generating the virtual module? Now thats why I still can find them now!
4) more needs to be done
Another thing I encountered is when several dynamically loaded modules have the same dependencies, webpack tries to generate more chunks with names like module1~module2.bundle.js
, breaking my build. To counter this, I needed to make sure that all those shared modules go into a specific named bundle I called "shared":
optimization:
splitChunks:
chunks: "all",
name: "shared"
And when in production mode, I simply load this chunk manually before any dynamic modules depending on it are requested:
if (!isDev)
await __webpack_require__.e("shared");
Again, this code only runs in production mode!
Finally, I have to prevent webpack renaming my modules (and chunks) to something like "1", "2" etc, but rather keep the names I just have defined:
optimization:
namedChunks: true,
namedModules: true
Se yeah, there you have it! As I said this wasn't pretty but seems to work, at least with my simplified test setup. I really hope there aren't any blockers ahead of me when I do all the rest (like ESLint, SCSS etc)!
add a comment |
So yeah, after much fiddling there seems to be a light at the end of the tunnel. Still, this is not a 100% solution and it is surely not for the faint of heart, as it is quite ugly and fragile. But still I want to share my approach with you:
1) manual parsing of my routes config
My router uses a config file looking like this:
import StaticClass from "/src/app/StaticClass.js";
export default
StaticClass:
match: /^//,
module: StaticClass
,
DynClass:
match: /^//,
module: "/src/app/DynClass.js"
;
So as you can see the export is an object, with keys acting as the route id, and an object that contains the matches (regex based) and the module which should be executed by the router if the route matches. I can feed my router with both a Constructor function (or an object) for modules which are available immediatly (i.e. contained in the main chunk) or if the module value is a string, this means that the router has to load this module dynamically by using the path specified in the string.
So as I know what modules could be potentially loaded (but not if and when) I can now parse this file within my build process and transform the route config to something webpack can understand:
const path = require("path");
const fs = require("fs");
let routesSource = fs.readFileSync(path.resolve(__dirname, "app/routes.js"), "utf8");
routesSource = routesSource.substr(routesSource.indexOf("export default"));
routesSource = routesSource.replace(/module:s*((?!".*").)*$/gm, "module: undefined,");
routesSource = routesSource.replace(/r?n|r/g, "").replace("export default", "var routes = ");
eval(routesSource);
let dummySource = Object.entries(routes).reduce((acc, [routeName, routeConfig]) =>
if (typeof routeConfig.module === "string")
return acc + `import(/* webpackChunkName: "$routeName" */"$routeConfig.module");`;
return acc;
, "") + "export default ''";
(Yeah I know this is quite ugly and also a bit brittle so this surely could be done better)
Essentially I create a new, virtual module where every route entry which demands a dynamic import is translated, so:
DynClass:
match: /^//,
module: "/src/app/DynClass.js"
becomes:
import(/* webpackChunkName: "DynClass" */"/src/app/DynClass.js");
So the route id simply becomes the name of the chunk!
2) including the virtual module in the build
For this I use the virtual-module-webpack-plugin
:
plugins: [
new VirtualModulePlugin(
moduleName: "./app/dummy.js",
contents: dummySource
)
],
Where dummySource
is just a string containing the sourcecode of my virtual module I just have generated. Now, this module is pulled in and the "virtual imports" can be processed by webpack. But wait, I still need to import the dummy module, but I do not have any in my development mode (where I use everything natively, so no loaders).
So in my main code I do the following:
let isDev = false;
/** @remove */
isDev = true;
/** @endremove */
if (isDev) import('./app/dummy.js');
Where "dummy.js" is just an empty stub module while I am in development mode. The parts between that special comments are removed while building (using the webpack-loader-clean-pragma
loader), so while webpack "sees" the import for dummy.js
, this code will not be executed in the build itself since then isDev
evaluates to false
. And since we already defined a virtual module with the same path, the virtual module is included while building just like I want, and of course all dependencies are resolved as well.
3) Handling the actual loading
For development, this is quite easy:
import routes from './app/routes.js';
Object.entries(routes).forEach(async ([routeId, route]) =>
if (typeof route.module === "function")
new route.module;
else
const result = await import(route.module);
new result.default;
);
(Note that this is not the actual router code, just enough to help me with my PoC)
Well, but for the build I need something else, so I added some code specific to the build environment:
/** @remove */
const result = await import(route.module);
new result.default;
/** @endremove */
if (!isDev)
if (typeof route.module === "string") await __webpack_require__.e(routeId);
const result = __webpack_require__(route.module.replace("/src", "."));
new result.default;
Now, the loading code for the dev environment is just stripped out, and there is another loading code that uses webpack internally. I also check if the module value is a function or string, and if it is the latter I invoke the internal require.ensure
function to load the correct chunk: await __webpack_require__.e(routeId);
. Remember that I named my chunks when generating the virtual module? Now thats why I still can find them now!
4) more needs to be done
Another thing I encountered is when several dynamically loaded modules have the same dependencies, webpack tries to generate more chunks with names like module1~module2.bundle.js
, breaking my build. To counter this, I needed to make sure that all those shared modules go into a specific named bundle I called "shared":
optimization:
splitChunks:
chunks: "all",
name: "shared"
And when in production mode, I simply load this chunk manually before any dynamic modules depending on it are requested:
if (!isDev)
await __webpack_require__.e("shared");
Again, this code only runs in production mode!
Finally, I have to prevent webpack renaming my modules (and chunks) to something like "1", "2" etc, but rather keep the names I just have defined:
optimization:
namedChunks: true,
namedModules: true
Se yeah, there you have it! As I said this wasn't pretty but seems to work, at least with my simplified test setup. I really hope there aren't any blockers ahead of me when I do all the rest (like ESLint, SCSS etc)!
So yeah, after much fiddling there seems to be a light at the end of the tunnel. Still, this is not a 100% solution and it is surely not for the faint of heart, as it is quite ugly and fragile. But still I want to share my approach with you:
1) manual parsing of my routes config
My router uses a config file looking like this:
import StaticClass from "/src/app/StaticClass.js";
export default
StaticClass:
match: /^//,
module: StaticClass
,
DynClass:
match: /^//,
module: "/src/app/DynClass.js"
;
So as you can see the export is an object, with keys acting as the route id, and an object that contains the matches (regex based) and the module which should be executed by the router if the route matches. I can feed my router with both a Constructor function (or an object) for modules which are available immediatly (i.e. contained in the main chunk) or if the module value is a string, this means that the router has to load this module dynamically by using the path specified in the string.
So as I know what modules could be potentially loaded (but not if and when) I can now parse this file within my build process and transform the route config to something webpack can understand:
const path = require("path");
const fs = require("fs");
let routesSource = fs.readFileSync(path.resolve(__dirname, "app/routes.js"), "utf8");
routesSource = routesSource.substr(routesSource.indexOf("export default"));
routesSource = routesSource.replace(/module:s*((?!".*").)*$/gm, "module: undefined,");
routesSource = routesSource.replace(/r?n|r/g, "").replace("export default", "var routes = ");
eval(routesSource);
let dummySource = Object.entries(routes).reduce((acc, [routeName, routeConfig]) =>
if (typeof routeConfig.module === "string")
return acc + `import(/* webpackChunkName: "$routeName" */"$routeConfig.module");`;
return acc;
, "") + "export default ''";
(Yeah I know this is quite ugly and also a bit brittle so this surely could be done better)
Essentially I create a new, virtual module where every route entry which demands a dynamic import is translated, so:
DynClass:
match: /^//,
module: "/src/app/DynClass.js"
becomes:
import(/* webpackChunkName: "DynClass" */"/src/app/DynClass.js");
So the route id simply becomes the name of the chunk!
2) including the virtual module in the build
For this I use the virtual-module-webpack-plugin
:
plugins: [
new VirtualModulePlugin(
moduleName: "./app/dummy.js",
contents: dummySource
)
],
Where dummySource
is just a string containing the sourcecode of my virtual module I just have generated. Now, this module is pulled in and the "virtual imports" can be processed by webpack. But wait, I still need to import the dummy module, but I do not have any in my development mode (where I use everything natively, so no loaders).
So in my main code I do the following:
let isDev = false;
/** @remove */
isDev = true;
/** @endremove */
if (isDev) import('./app/dummy.js');
Where "dummy.js" is just an empty stub module while I am in development mode. The parts between that special comments are removed while building (using the webpack-loader-clean-pragma
loader), so while webpack "sees" the import for dummy.js
, this code will not be executed in the build itself since then isDev
evaluates to false
. And since we already defined a virtual module with the same path, the virtual module is included while building just like I want, and of course all dependencies are resolved as well.
3) Handling the actual loading
For development, this is quite easy:
import routes from './app/routes.js';
Object.entries(routes).forEach(async ([routeId, route]) =>
if (typeof route.module === "function")
new route.module;
else
const result = await import(route.module);
new result.default;
);
(Note that this is not the actual router code, just enough to help me with my PoC)
Well, but for the build I need something else, so I added some code specific to the build environment:
/** @remove */
const result = await import(route.module);
new result.default;
/** @endremove */
if (!isDev)
if (typeof route.module === "string") await __webpack_require__.e(routeId);
const result = __webpack_require__(route.module.replace("/src", "."));
new result.default;
Now, the loading code for the dev environment is just stripped out, and there is another loading code that uses webpack internally. I also check if the module value is a function or string, and if it is the latter I invoke the internal require.ensure
function to load the correct chunk: await __webpack_require__.e(routeId);
. Remember that I named my chunks when generating the virtual module? Now thats why I still can find them now!
4) more needs to be done
Another thing I encountered is when several dynamically loaded modules have the same dependencies, webpack tries to generate more chunks with names like module1~module2.bundle.js
, breaking my build. To counter this, I needed to make sure that all those shared modules go into a specific named bundle I called "shared":
optimization:
splitChunks:
chunks: "all",
name: "shared"
And when in production mode, I simply load this chunk manually before any dynamic modules depending on it are requested:
if (!isDev)
await __webpack_require__.e("shared");
Again, this code only runs in production mode!
Finally, I have to prevent webpack renaming my modules (and chunks) to something like "1", "2" etc, but rather keep the names I just have defined:
optimization:
namedChunks: true,
namedModules: true
Se yeah, there you have it! As I said this wasn't pretty but seems to work, at least with my simplified test setup. I really hope there aren't any blockers ahead of me when I do all the rest (like ESLint, SCSS etc)!
answered Mar 16 at 16:47
frontend_devfrontend_dev
1,1551121
1,1551121
add a comment |
add a comment |
Thanks for contributing an answer to Stack Overflow!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f55052636%2fhow-to-include-manual-import-in-webpack-bundle%23new-answer', 'question_page');
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Have you looked at webpack's explanation of the subject?
– flup
Mar 13 at 22:27
2
there are many unclear things here, how do you use
import()
in your code? also where is/src/app/DynClass.js
used?– d7my
Mar 14 at 12:01
@flup: yes I have. @d7my
import()
is used like this:const module = await import(modulePath);
, so I cannot use a template here.DynClass
is just a test-dummy being imported by the code I just mentioned. So there will be a kind of configuration object so my app-router knows what routes to match and what module to import.– frontend_dev
Mar 14 at 12:40