Dynamically load chunks in webpack? - javascript

We load files dynamically i.e., we don't know which files will be loaded until runtime. At the same, for faster loading, we'd like to put related files in the same chunk.
How can I do that with webpack?
This is what we have and it's failing with a 404 error (1.1.bundle.js not found)
This is what webpack.config looks like:
entry: {
main: //...,
related_files: [ //should create chunk for file1 and file2?
'./file1.js',
'./file2.js'
]
},
This is what the code to dynamically load the files looks like:
var dynamicFileName = //...
require.ensure([], function (require) {
//should dynamically load the chunk containing dynamicFileName?
//fails with 'file1.js' or 'file2.js'
var modImpl = require(dynamicFileName);
//...
});
Update 1: the error message is caused by not configuring output.publicPath. However, I never created 1.1.bundle.js. It seems to be ignoring the entry point.
Update 2: even after fixing output.publicPath, it's unable to load a dynamically generated filename. So it seems that webpack cannot handle this.

By default, webpack tries to bundle all the code in a single file. If you're using code from file1.js/file2.js in main entry point, webpack will bundle contents of all the files in main.js, and second entry point related_files will output only file1/file2 contents.
Webpack handles this situation by using CommonsChunkPlugin, your config must look like this:
entry: {
main: //...,
related_files: ['./file1.js','./file2.js']
},
plugins: [
new webpack.optimize.CommonsChunkPlugin('related_files', 'related_files.js')
]
Second part of the question is that webpack parses require statement, and outputs 1.1.bundle.js - the dynamic module, that can be loaded with require in the code. In your case, dynamicFileName = 'related_files', not file1/file2.
Please see http://webpack.github.io/docs/code-splitting.html#split-app-and-vendor-code

Related

Get original file path from within Webpack loader

I'm building a custom Webpack loader. What the loader does is unimportant, but it transforms JSON in some way and uses paths from the JSON in order to resolve certain other details. In my loader.js I need a way of getting the original path of the JSON file being loaded such that I can resolve other paths properly.
Take this simple loader and config:
loader.js
module.exports = function (source) {
/* Do some file lookups based on source and modify source */
this.callback(null, source)
}
webpack.config.js
module.exports = {
/* ... */
module: {
rules: [
{
test: /\.json$/i,
use: ['loader'],
},
],
},
};
The loader is working (being used), but any business logic I add needs to be path-aware such that it can do lookups on the file system.
Because this is a JSON loader, the source var in the loader function is passed as raw JSON content, with no details about which file the JSON was loaded from. How does one go about getting path information from within the loader such that I can perform other file lookups based on the content from source?
It turns out the property I was looking for is available via the execution context property this, as resourcePath.
module.exports = function (source) {
console.log('The original file was here:', this.resourcePath)
this.callback(null, source)
}
Here is the (rather sparse) documentation.
If anybody can think of a better/more future-proof way of getting this path then feel free to comment or post another answer.

webpack - transpile 1 ts file without bundling it (2 entries)

TL;DR
(vue files) + background.ts => ...[webpack]... => (bundled vue files) + background.js
can't execute background.js
expected background.js to contain only "console.log('test');"
I have a vue project with webpack and typescript.
I want my build step to produce a "background.js" file aside from the [vue JS related files].
I have a source file in typescript: "background.ts".
Through the vue.config.js I added a webpack entry "background".
It does build a file "background.js" as I expected
but it is bundled(I think) and it can't be executed by a chrome plugin.
For now all I want is to have a "background.js" file which execute the "console.log('test');" instruction it contains when the script is called.
Thank you, webpack is hell
edit: adding files:
// vue.config.js
const CopyWebpackPlugin = require('copy-webpack-plugin');
module.exports = {
filenameHashing: false,
chainWebpack: config => {
// add your custom entry point
config
.entry('background')
.add('./src/background.ts');
},
configureWebpack: {
plugins: [
new CopyWebpackPlugin([
{ from: 'manifest.json', to: 'manifest.json', flatten: true },
]),
]
}
}
content of "$vue inspect"
$vue inspect > https://pastebin.com/6F3zwLhC
What I tried:
exporting a function instead of my plain code:
export default function() {
console.log("gboDebug: background.ts dans export function");
}
// instead of just
console.log("gboDebug: background.ts dans export function");
at the end of the file adding this because I saw it somewhere:
export default null;
checked that my console.log was in the background.js bundled file
pasted the result of background.js in the navigator
played with the webpackJsonp global var the scripts creates
What I thought about:
having a npm script which 1-bundle-vue-webpack and then 2-transpile my file with babel-loader
playing with the library output option in webpack but I think it makes code available for use in a variable, it doesn't auto-execute code when loaded
webpack output in IIFE: https://webpack.js.org/configuration/output/#outputiife
In short – you don't need a bundler for transpiling a single typescript file. Just use tsc.
Specifically to this question where the Vue app is used as part of chrome extension, it may make sense to separate building an app and the extension related files.
Another possible option is to use something like Vue CLI Browser Extension Plugin.

ES6 Dynamic Imports using Webpack and Babel

I've been using Webpack for my ES6 JS project and has been going well until I started to play with dynamic imports.
What I had that worked (router.js):
import { navigo } from "Navigo"; // router
import { clients } from "Controllers/clients.js";
const navigo = new Navigo();
navigo_router.on({
'/clients': () => {
clients.init();
}
});
But the more pages/routes I add, the more imports get stacked up in the head of the module. This is a relatively large app and I have a lot of pages/routes to add and therefore I need to load them dynamically to reduce the size of the initial page load.
So, following Webpack's documentation for dynamic imports, I tried the following which loads the controller module only when the relative route is called:
import { navigo } from "Navigo"; // router
const navigo = new Navigo();
navigo_router.on({
'/clients': () => {
import("Controllers/clients.js").then((clients) => {
clients.init();
});
}
});
But saving this in my editor resulted in a Babel transpiling error; SyntaxError: 'import' and 'export' may only appear at the top level, and clients.init() is not being called when tested in browser.
After a bit of reading, I discovered I needed a Babel plugin to transpile dynamic import() to require.ensure. So, I installed the plugin using the following command:
npm install babel-plugin-dynamic-import-webpack --save-dev
And declared the plugin in my babel.rc file
{ "plugins": ["dynamic-import-webpack"] }
After installing the plugin, the transpiling error disappeared and checking my transpiled code I found that the dynamic import()s has in fact been changed to require.ensure as expected. But now I get the following browser errors when testing:
Error: Loading chunk 0 failed.
Stack trace:
u#https://<mydomain.com>/js/app.bundle.js:1:871
SyntaxError: expected expression, got '<' 0.app.bundle.js:1
Error: Loading chunk 0 failed.
I didn't understand why it was referencing 0.app.bundle.js with the 0. prefix, so I checked my output/dist folder and I now have a new file in there called 0.app.bundle.js:
0.app.bundle.js 1,962bytes
app.bundle.js 110,656bytes
I imagine this new bundled file is the dynamically imported module, clients.js.
I only added dynamic importing to that one route and have left all the other routes as they were. So, during testing, I can view all routes except that one /clients route that now throws the above errors.
I'm totally lost at this point and hoped somebody could help push me over the finish line. What is this new file 0.app.bundle.js and how am I supposed to be using it/including it in my application?
I hope I've explained myself clearly enough and look forward to any responses.
I managed to fix my own problem in the end, so I will share what I discovered in an answer.
The reason the chunk file wasn't loading was because Webpack was looking in the wrong directory for it. I noticed in the Network tab of my developer console that the the chunk file/module was being called from my root directory / and not in /js directory where it belongs.
As per Webpack's documentation, I added the following to my Webpack config file:
output: {
path: path.resolve(__dirname, 'dist/js'),
publicPath: "/js/", //<---------------- added this
filename: 'app.bundle.js'
},
From what I understand, path is for Webpack's static modules and publicPath is for dynamic modules.
This made the chunk load correctly but I also had further issues to deal with, as client.init() wasn't being called and yielded the following error:
TypeError: e.init is not a function
To fix this, I also had to change:
import("Controllers/clients.js").then((clients) => {
clients.init();
});
To:
import("Controllers/clients.js").then(({clients}) => {
clients.init();
});
Note the curly braces in the arrow function parameter.
I hope this helps somebody else.
For debugging, you need to do
import("Controllers/clients.js").then((clients) => {
console.log(clients);
});
maybe working
import("Controllers/clients.js").then((clients) => {
clients.default.init();
});

Webpack to simply compile a bunch of Pug templates to HTML

Im getting started with webpack but one thing I cannot for the life of me work out is how to take a folder (with possible nested folders), full of .pug templates, and simply compile them to static html and put them in the output folder, maintaining any nested folder structure for each output html file that was in the source templates folder...
I dont want to have to manually specify each individual .pug file, and I definitely dont want webpack to try and parse the .pugs into JS and then attempt to require/import any of the imgs/fonts etc in the pug files and then complain about it, Im just after a basic, static 1:1 compile, pug file in, html file out. Why is it so hard to do that?
Use pug-html-loader to convert .pug to .html file. Use file-loader to copy the file to desired location. Don't use html-loader as you don't want to process resources used by the generated file.
You will end up something like this in your loader rules (untested, webpack 1 syntax, you may need to tweak it for webpack 2)
{
test: /\.pug$/,
loaders: ['file-loader?name=[path][name].html', 'pug-html-loader?pretty&exports=false']
}
Next you need to require all your pug files in your entry file
function requireAll (r) { r.keys().forEach(r); }
requireAll(require.context('./', true, /\.pug$/));
This can be done very simply with only html-webpack-plugin and pug-loader.
webpack.config.js
const HTMLWebpackPlugin = require('html-webpack-plugin');
module.exports = {
// No javascript entrypoint to evaluate. Let the plugin do the heavy lifting
entry: {},
// Translate Pug to HTML
module: { rules: [ { test: /\.pug$/, use: 'pug-loader' } ] },
// Save HTML to file
plugins: [ new HTMLWebpackPlugin({ template: './src/index.pug' }) ]
};
./src/index.pug
doctype html
html(land="en")
head
include path/to/another.pug
...
Got this information from https://extri.co/2017/05/23/using-htmlwebpackplugin-and-pug/ and you can also go further to import css and javascript as normally done with html-webpack-plugin.

Grunt + Concat + Angularjs

Setup:
A Gruntfile with the following task:
concat: {
build: {
files: {
'build/app.js': [
'src/.js',
'src//.js',
'!src/vendors/'
],
}
}
A lot of angular modules, with its controllers, services, and so on, with a structure like this:
a/
a.js // Module declaration like: angular.module('a',[])
a-controller.ks // Which sets a controller in its root module definition like: angular.module('a').controller()...
Issue:
The task concatenates all the js files it finds in the build folder to a single app.js file, and it does this fine, but messes up with the order of files when concatenating.
For instance, it concatenates first the controller file instead of the main folder file containing the module declaration, triggering the following error:
Module xxxx not available!
I suppose the issue lies in the way concat builds up the files and that is done by the grunt core and specifically the minimatch library, and the possibility it treats dashes to be first than letters, but I don't know how configure to change that behavior, and even know if that is possible.
Question:
So, the question is: How can I make Grunt/Grunt-concat to process dashed f first than the others in the same folder so the ordering is maintained?
Thanks
Update 1:
After digging more, It seems that it has nothing to do with the ordering inside a folder, but Grunt/Core sending the root files to the end and putting them the leaf ones first.
Just specify the order you want to concat your files, placing them in order, what I mean is, first add your single files that should be concatenated at start, after your full folder that does not need to have an order, and finally your final files, something rougth like this:
grunt.initConfig({
concat: {
js: {
src: ['lib/before.js', 'lib/*', 'lib/after.js'],
dest: 'bundle.js',
}
}
});
You will have to specify to the grunt-concat task the order you want your files built. For my projects, I typically keep a folder structure where controllers go in a app/controllers folder, services in services, and etc, but names can vary. I also keep an app.js that declares my app module and specifies the config handler for it. I use a config like this for grunt-uglify but the same can be done for concat with little to no changes:
uglify: {
development: {
files: {
'public/scripts/app.js': [
'public/app/app.js',
'public/app/controllers/*.js',
'public/app/directives/*.js',
'public/app/services/*.js'
]
}
}
}
I just copy paste my answer, the detail you want on second picture, i hope help you.
you may consider this solution
Separate the module declaration to xxx.module.js
In grunt-contrib-concat modify the config like below :
place this outside grunt.initConfig
var clientApp = './app/';
grunt-contrib-concat config
dist: {// grab module first, state the second
src: [
clientApp+'**/*-controller.js',
clientApp+'**/*.module.js',
clientApp+'**/*.state.js',
clientApp+'**/*.js'
],
dest: 'dist/<%= pkg.name %>.js'
}
i use state to so i have to define state too before trying to navigate to any state. This is preview my code, the module declaration is declared fist before anything, then my state. even minified doesnt create any problem.
I hope this help you.
i follow this johnpapa's style guide, your problem might solve there if my solution not work

Categories

Resources