Code Splitting with Webpack for Intl polyfill - javascript

I'm trying to create a bundle for the intl polyfill to load it only for browser which requires it.
Using the following code, it creates the bundle in my output folder
if (!window.Intl) {
require.ensure(['intl/dist/Intl'], (require) => {
window.Intl = require('intl/dist/Intl');
}, 'intl-bundle');
}
The first thing I noticed is the size of the file is huge, it contains all the languages, is there a way to define the languages I want?
The other issue is that when the browser tries to download the file, it tries from the root folder of my app. So instead of downloading http://mydomain/js/1.intl-bundle.js, it tries to get http://mydomain/1.intl-bundle.js
Is there a way to set from where the file should be downloaded?
My webpack.config.js output looks like this:
output: {
path: './js/',
filename: '[name].js'
},
And the last question, can I remove the 1. at the beginning of the name of the file, just to have intl-bundle.js?
Thanks

I'm actually going to answer a few of my own question as I just found some of the solution after a few hours of research.
For the second question, I wasn't defining the publicPath in my build, which was the reason why the browser was downloading the file from the root of my domain. More info: https://github.com/webpack/docs/wiki/configuration#outputpublicpath
output: {
path: './js/',
publicPath: './js/'
}
For the last question, you can name your chunks with the following:
output: {
chunkFilename: '[id]-[name]-[chunkhash].js'
}
This allows you to decide the name of your chunks.
I still don't have any answer to reduce the size of the intl file.

Related

What is the difference between multi-compiler and multiple-entry-points in webpack?

guys!! I'm trying to learn webpack and optimize my webpack project(a mutil-page project) configuration, but I got confused in some problems.hope someone can help me.
By the way,please forgive me for not having a good English, but I think Google Translate should basically be able to let me explain my problem. If there is something unclear in the description, please point it out and I will modify it.
Related Links
https://github.com/webpack/webpack/blob/main/examples/multiple-entry-points/webpack.config.js
https://github.com/webpack/webpack/blob/main/examples/multi-compiler/webpack.config.js
Q1.What are the advantages and disadvantages of each in mutil-html page config?
maybe multi-compiler have more flexible configuration but speed slower?
Q2.If I use the configuration of mutil-entry-point, how can I get the current entry name in the plugin or loader?
I know that the author of webpack said that this can't be done on the loader,
https://github.com/webpack/webpack/issues/6124#issuecomment-351647892
but how about in plugin?
Q3.Is there any way to get the entire dependency graph from the entry?
like webpack-bundle-analyzer?
The MultiCompiler allows you to create different rulesets and use different plugins for different files where the multi-target just allows you to compile multiple files and outputs. As for your questions:
Basically yes, multi-compiler does not run in parallel and needs to load the configuration, plugins and rulesets for each compilation. So it is more flexible, but runs slower, there are however alternatives to run it in parallel like parallel-webpack
You can create a custom plugin for your webpack and use their compiler-hooks to get what you want, more specifically I would check out the asset-emitted hook, that looks as follows:
compiler.hooks.assetEmitted.tap(
'MyPlugin',
(file, { content, source, outputPath, compilation, targetPath }) => {
console.log(content); // <Buffer 66 6f 6f 62 61 72>
}
);
If you wish to create your own plugin, this guide of theirs is essential: webpack writing-a-plugin
The best I can come up with is this from webpack-bundle-analyzer npm package :
When opened, the report displays all of the Webpack chunks for your project. It's possible to filter to a more specific list of chunks by using the sidebar or the chunk context menu.
Sidebar
The Sidebar Menu can be opened by clicking the > button at the top left of the report. You can select or deselect chunks to display under the "Show chunks" heading there.
Chunk Context Menu
The Chunk Context Menu can be opened by right-clicking or Ctrl-clicking on a specific chunk in the report. It provides the following options:
Hide chunk: Hides the selected chunk
Hide all other chunks: Hides all chunks besides the selected one
Show all chunks: Un-hides any hidden chunks, returning the report to its initial, unfiltered view
They clarify it in their documentation:
MultiCompiler:
The MultiCompiler module allows webpack to run multiple configurations in separate compilers. If the options parameter in the webpack's NodeJS api is an array of options, webpack applies separate compilers and calls the callback after all compilers have been executed.
var webpack = require('webpack');
webpack([
{ entry: './index1.js', output: { filename: 'bundle1.js' }, ruleset1, pluginsA},
{ entry: './index2.js', output: { filename: 'bundle2.js' }, ruleset2, pluginsB }
], (err, stats) => { // [Stats Object](#stats-object)
process.stdout.write(stats.toString() + '\n');
})
Multiple-entrypoints
If your configuration creates more than a single "chunk" (as with multiple entry points or when using plugins like CommonsChunkPlugin), you should use substitutions to ensure that each file has a unique name.
module.exports = {
entry: {
app: './src/app.js', // Will export to app.js
search: './src/search.js', // Will export to search.js
},
output: {
filename: '[name].js',
path: __dirname + '/dist',
},
};
Also, check out webpack-code splitting

Change domain of images that webpack generates for imported images

Although my dev server is running on localhost:3000, I have set up my host file to point www.mysite.com to localhost. In my JavaScript, I have code like:
import myImage from '../assets/my-image.jpg'
const MyCmp => <img src={myImage} />
Using Webpack's file-loader, it transforms that import into a URL to the hosted image. However, it uses the localhost path to that image, but I'd like it to use the www.mysite.com domain. I looked at both the publicPath and postTransformPublicPath options for file-loader, but those only appear to allow you to modify the part of the path that comes after the domain.
I personally don't like the notion of defining host-information statically in the build output. This is something that should be determined in runtime based on where you actually put your files.
If you are like me then there are two options here.
Both involve you calling a global method that you have defined on i.e. window / global scope.
The purpose of the global method is to resolve the root path (the domain, etc) in runtime.
Define a global method
So lets say you define a method on the global scope somewhere in your startup code like so:
(<any>window).getWebpackBundleRootPath = function (webpackLibraryId) {
if (webpackLibraryId == null) return throw "OOOPS DO SOMETHING HERE!";
// Preferably these variables should be loaded from a config-file of sorts.
if(webpackLibraryId == "yourwebpacklibrary1") return "https://www.yoursite.com/";
// If you have other libraries that are hosted somewhere else, put them here...
return "...some default path for all other libraries...";
};
The next step is to configure webpack to call this global method when it tries to resolve the path.
As I mentioned there are two ways, one that manipulates the output of the webpack and one that is more integrated in webpacks configuration (although only for file-loader but I think it should suffice).
It's worth mentioning that you don't need a global method if you only have one bundle or if you host all your bundles in one place. Then it would be enough to use a global variable instead. It should be quite easy to modify the example below to accommodate this.
First option: configure webpack file-loader to call your method when resolving path
This solution will not require something to be done post build. If this fits your need and covers all scenarios I would go for this option.
Edit your webpack config file
var path = require('path');
let config = {
entry: {
'index': path.join(__dirname, '/public/index.js')
},
output: {
path: path.join(__dirname, '/dist/'),
filename: 'index-bundle.js',
publicPath: 'https://localhost:3000/',
library: 'yourwebpacklibrary1',
...
},
module: {
rules: [{
// Please note that this only defines the resolve behavior for ttf. If you want to resolve other files you need to configure the postTransformPublicPath for those too. This is a big caveat in my opinion and might be a reason for using second option.
test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/,
use: [{
loader: 'file-loader',
options: {
outputPath: 'assets/fonts', // The folder where you store your fonts.
name: '[name].[ext]',
// This is where the magic happens. This lets you override the output of webpack file resolves.
postTransformPublicPath: function (p) {
// Because of the way webpack file-loader works the input from to this method will look something like this: __webpack_public_path__ + "/assets/fonts/yourfont.ttf"
// But we are not interested in using the __webpack_public_path__ variable so lets remove that.
p = p.replace('__webpack_public_path__ + ', '');
// Return a stringified call to our global method and append the relative path to the root path returned.
return `getWebpackBundleRootPath("${config.output.library}") + ${p}`;
}
}
}]
},
},
...
};
module.exports = config;
As you might have noticed in the comments in the webpack config file you need to specify the resolve behavior for each file-loader that you add (if someone knows a better way, please let me know). This is why I still use the second option.
Second option: manipulate the output of the webpack in a postbuild step
Example webpack.config.js file
For completeness sake here is an example of a webpack.config.js file that contains the variables used in the postbuild script.
var path = require('path');
module.exports = {
entry: {
'index': path.join(__dirname, '/public/index.js')
},
output: {
path: path.join(__dirname, '/dist/'),
filename: 'index-bundle.js',
publicPath: 'https://localhost:3000/',
library: 'yourwebpacklibrary1',
...
},
...
}
Create a postbuild.js file
Create a file postbuild.js next to your package.json with the following content:
const fs = require('fs');
// We take the path to the webpack config file as input so that we can read settings from it.
const webpackConfigFile = process.argv[2];
// Read the webpack config file into memory.
const config = require(webpackConfigFile);
// The file to manipulate is the output javascript bundle that webpack produces.
const inputFile = config.output.path + config.output.filename;
// Load the file into memory.
let fileContent = fs.readFileSync(inputFile, 'utf8');
// Replace the default public path with the call to the method. Please note that if you specify a publicPath as '/' or something very common you might end up with a problem so make sure it is unique in the file to avoid other unrelated stuff being replaced as well.
fileContent = fileContent.replace('"' + config.output.publicPath + '"', 'getWebpackBundleRootPath("' + config.output.library + '")');
// Save the manipulated file back to disk.
fs.writeFileSync(inputFile, fileContent, 'utf8');
Call the postbuild.js automatically on build
Next step is to actually call the postbuild.js script after each build.
This can be done in a postscript in package.json like so (in the script section in your package.json):
{
"scripts": {
"build": "webpack",
"postbuild": "node postbuild.js ./webpack.config.js"
}
}
From now on whenever you run the build script it will also run the postbuild script (from npm or yarn, etc).
You can of course also manually run the postbuild.js script manually after each build instead.
but those only appear to allow you to modify the part of the path that comes after the domain.
Not really, you can give it an URL that includes the domain.
In your case, assuming your images are under the assets directory, you will have something like this in your webpack.config.js
...
module: {
rules: [
...
{
test: /\.(png|jpe?g|gif|svg)$/,
use: {
loader: 'file-loader',
options: {
publicPath: 'https://www.example.com/assets',
outputPath: 'assets'
}
}
},
...
]
}
...

Multiple entry points without common chunks or dynamic loading

I'm making a chrome extension that hosts multiple applications (multiple devtools panels), each one independent. I need webpack to watch multiple entry points and produce multiple bundles, I don't need common chunks. I definitely do not want to use amd in the client side code like this.
/appOne
index.js
/components
/blah
/foobar
/appTwo
index.js
/components
..etc
/appThree
index.js
/components
..etc.
/chrome <- basically the "dist" folder
/appOne
index.html
bundle.js
/appTwo
index.html
bundle.js
/appThree
etc...
As per docs on multiple entries I've been doing:
{
entry: {
appOne: './appOne/index.js',
appTwo: './appTwo/index.js'
},
output: {
path: path.join(__dirname, 'chrome', '[name]'),
filename: 'bundle.js' //can be the same since they should be output in different folders
}
}
I get the error:
Path variable [name] not implemented in this context: C:\Users\Admin\projects\crx\chrome\[name]
So I guess you cannot have the [name] variable in the path setting for multiple entries?
You should use [name] in the filename field instead of path. Looking at the docs, filename lists the [name] variable and path does not (only showing [hash]).
You would use something like:
{
path: path.join(__dirname, 'chrome'),
filename: '[name]/bundle.js'
}
The documentation does not explicitly state the filename can have multiple path segments, it only says the filename must not be an absolute path.

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

Simple solution to share modules loaded via NPM across multiple Browserify or Webpack bundles

Pulling my hair out here looking for a simple solution to share code, required via NPM, across multiple Browserify or Webpack bundles. Thinking, is there such a thing as a file "bridge"?
This isn't due to compile time (I'm aware of watchify) but rather the desire to extract out all of my vendor specific libs into vendor.js so to keep my app.js filesize down and to not crash the browser with massive sourcemaps. Plus, I find it way cleaner should the need to view the compiled js arise. And so:
// vendor.js
require('react');
require('lodash');
require('other-npm-module');
require('another-npm-module');
Its very important that the code be loaded from NPM as opposed to Bower, or saved into some 'vendor' directory in order to be imported via a relative path and identified via a shim. I'd like to keep every library reference pulled via NPM except for my actual application source.
In app.js I keep all of my sourcecode, and via the externals array, exclude vendor libraries listed above from compilation:
// app.js
var React = require('react');
var _ = require('lodash');
var Component = React.createClass()
// ...
And then in index.html, I require both files
// index.html
<script src='vendor.js'></script>
<script src='app.js'></script>
Using Browserify or Webpack, how can I make it so that app.js can "see" into those module loaded via npm? I'm aware of creating a bundle with externals and then referencing the direct file (in, say, node_modules) via an alias, but I'm hoping to find a solution that is more automatic and less "Require.js" like.
Basically, I'm wondering if it is possible to bridge the two so that app.js can look inside vendor.js in order to resolve dependencies. This seems like a simple, straightforward operation but I can't seem to find an answer anywhere on this wide, wide web.
Thanks!
Listing all the vendor files/modules and using CommonChunkPlugin is indeed the recommended way. This gets pretty tedious though, and error prone.
Consider these NPM modules: fastclick and mprogress. Since they have not adopted the CommonJS module format, you need to give webpack a hand, like this:
require('imports?define=>false!fastclick')(document.body);
require('mprogress/mprogress.min.css');
var Mprogress = require('mprogress/mprogress.min.js'),
Now assuming you would want both fastclick and mprogress in your vendor chunk, you would probably try this:
module.exports = {
entry: {
app: "./app.js",
vendor: ["fastclick", "mprogress", ...]
Alas, it doesn't work. You need to match the calls to require():
module.exports = {
entry: {
app: "./app.js",
vendor: [
"imports?define=>false!fastclick",
"mprogress/mprogress.min.css",
"mprogress/mprogress.min.js",
...]
It gets old, even with some resolve.alias trickery. Here is my workaround. CommonChunkPlugin lets you specify a callback that will return whether or not you want a module to be included in the vendor chunk. If your own source code is in a specific src directory, and the rest is in the node_modules directory, just reject the modules based on their path:
var node_modules_dir = path.join(__dirname, 'node_modules'),
app_dir = path.join(__dirname, 'src');
module.exports = {
entry: {
app: "./app.js",
},
output: {
filename: "bundle.js"
},
plugins: [
new webpack.optimize.CommonsChunkPlugin(
/* chunkName= */"vendor",
/* filename= */"vendor.bundle.js"
function (module, count) {
return module.resource && module.resource.indexOf(app_dir) === -1;
}
)
]
};
Where module.resource is the path to the module being considered. You could also do the opposite, and include only the module if it is inside node_modules_dir, i.e.:
return module.resource && module.resource.indexOf(node_modules_dir) === 0;
but in my situation, I'd rather say: "put everything that is not in my source source tree in a vendor chunk".
Hope that helps.
With webpack you'd use multiple entry points and the CommonChunkPlugin.
Taken from the webpack docs:
To split your app into 2 files, say app.js and vendor.js, you can require the vendor files in vendor.js. Then pass this name to the CommonChunkPlugin as shown below.
module.exports = {
entry: {
app: "./app.js",
vendor: ["jquery", "underscore", ...],
},
output: {
filename: "bundle.js"
},
plugins: [
new webpack.optimize.CommonsChunkPlugin(
/* chunkName= */"vendor",
/* filename= */"vendor.bundle.js"
)
]
};
This will remove all modules in the vendor chunk from the app chunk. The bundle.js will now contain just your app code, without any of it’s dependencies. These are in vendor.bundle.js.
In your HTML page load vendor.bundle.js before bundle.js.
<script src="vendor.bundle.js"></script>
<script src="bundle.js"></script>
// vendor anything coming from node_modules
minChunks: module => /node_modules/.test(module.resource)
Source: https://github.com/webpack/webpack/issues/2372#issuecomment-213149173

Categories

Resources