webpack import only variable value - javascript

I'm compiling code that needs the version value from package.json:
import {version} from '../package.json';
export default {version};
and when I look at the .js file that webpack outputs I see the whole package.json there!
How can I avoid this?
My setup is:
plugins: [
new webpack.DefinePlugin({
'process.env.NODE_ENV': '"production"'
}),
new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: false
}
}),
new CompressionPlugin({
asset: '[path].gz[query]',
algorithm: 'gzip',
test: /\.(js|css)$/,
threshold: 10240,
minRatio: 0.8
}),
]
My webpack version is 3.8.1

What I usually do is take advantage of the DefinePlugin
// webpack.config.js
// getting the version
const package = require("./package.json");
const version = package.version;
plugins: [
new webpack.DefinePlugin({
'process.env.NODE_ENV': '"production"',
'process.env.VERSION': version,
}),
new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: false
}
}),
new CompressionPlugin({
asset: '[path].gz[query]',
algorithm: 'gzip',
test: /\.(js|css)$/,
threshold: 10240,
minRatio: 0.8
}),
]
Now in your code all you gotta do is use process.env.VERSION and you it will output your packages version.
Hope it helped

Modern webpack versions support Tree shaking (https://webpack.js.org/guides/tree-shaking/ ), but it works only if export directives are configured by special scheme, includes independent named import for each entity. In that case webpack can perform analyze dependencies graph and include only required entities.
Also, import directive does not support destructing - it's only syntax for named import, so large JS object will be always imported in monolithic style.
Import as value is unavailable by definition, because webpack performs only bundling for set of files with source code and maybe custom resources dependencies. Real imports in ES6 modules, which today are already supported on most platforms, also do not provide values imports - instead it's binding. https://ponyfoo.com/articles/es6-modules-in-depth#bindings-not-values.
Of course, original problem can be solved by webpack replace or alias plugins. Just store version in some independent file or string constant and substitute it due bundling. Most straightforward solution is
import version from 'PACKAGE_VERSION'
and then configure external with callback https://webpack.js.org/configuration/externals/ like that
externals: [
function(context, request, callback) {
if (request === 'PACKAGE_VERSION'){
return callback(null, 'exports.default = ' + JSON.stringify(JSON.parse(fs.readFileSync('package.json')).version));
}
callback();
}
],

Related

Tree-shaking with rollup

I have a project in which I bundle a components library using Rollup (generating a bundle.esm.js file). These components are then used in another project, that generates web pages which use these components - each page is using different components.
The problem is, that the entire components library is always bundled with the different page bundles, regardless of which components I'm using, unnecessarily increasing the bundle size.
This is my Rollup setup:
import resolve from 'rollup-plugin-node-resolve';
import commonjs from 'rollup-plugin-commonjs';
import babel from 'rollup-plugin-babel';
import peerDepsExternal from 'rollup-plugin-peer-deps-external';
import pkg from './package.json';
const extensions = [
'.js', '.jsx', '.ts', '.tsx',
];
export default [
{
input: './src/base/index.ts',
plugins: [
peerDepsExternal(),
resolve({ extensions }),
babel({
exclude: 'node_modules/**',
extensions,
}),
commonjs(),
],
output: [
{ file: pkg.main, format: 'cjs', sourcemap: true },
{ file: pkg.module, format: 'es', sourcemap: true },
],
watch: {
clearScreen: false,
},
},
];
I have "modules" set to false in webpack, as well.
There are things you will need to do to achieve treeshakable code from both sides - the built package and the project using it.
From your code snippet, I see that you have not add flag preserveModules: true in the rollup config file to prevent the build output from bundling. Webpack can not treeshake a bundled file FYI.
export default {
...
preserveModules: true,
...
}
On the side of the project that using it, you have to specify sideEffects in the package.json - read the doc to know how to config them. Beside that, the optimization in webpack has to has sideEffects: true, also read the doc here.
Hope this helps!
As you don't know which components of your Component Library (CL) will be needed by the adopters repositories you need to export everything but in a way
the adopters can execute a tree-shaking on your CL when they do their own build (and just include what they really need).
In a few words, you have to make your CL, tree-shakable. In order to achieve this, on your CL repo you have to:
Use bundlers that support tree-shaking (rollup, webpack, etc..)
Create the build for modules of type es/esm, NOT commonJS/cjs, etc..
Ensure no transpilers/compilers (babel,tsconfig, etc..) usually used as plugins, transform your ES module syntax to another module syntax.
By the default, the behavior of the popular Babel preset #babel/preset-env may break this rule, see the documentation for more details.
// babelrc.json example that worked for me
[
"#babel/preset-env",
{
"targets": ">0.2%, not dead, not op_mini all"
}
],
In the codebase, you always have to use import/export (no require) syntax, and import specifically the things you need only.
import arrayUtils from "array-utils"; //WRONG
import { unique, implode, explode } from "array-utils"; //OK
Configure your sideEffects on the package.json.
"sideEffects": ["**/*.css"], //example 1
"sideEffects": false, //example 2
DO NOT create a single-bundle file but keep the files separated after your build process (official docs don't say this but was the only solution that worked for me)
// rollup.config.js example
const config = [
{
input: 'src/index.ts',
output: [
{
format: 'esm', // set ES modules
dir: 'lib', // indicate not create a single-file
preserveModules: true, // indicate not create a single-file
preserveModulesRoot: 'src', // optional but useful to create a more plain folder structure
sourcemap: true, //optional
},
],
... }]
Additionally, you may need to change your module entry point in order the adopters can directly access to the proper index.js file where you are exporting everthing:
// package.json example
{
...
"module": "lib/index.js", //set the entrypoint file
}
Note: Remember that tree-shaking is executed by an adopter repository that has a build process that supports tree-shaking (eg: a CRA repo) and usually tree-shaking is just executed on prod mode (npm run build), no on dev mode. So be sure to properly test if this is working or not.

UglifyJS webpack plugin throws: Unexpected token: name (features)

I used to have problems with UglifyJS for Webpack and ES6 modules:
ERROR in static/js/vendor.6ccd9e38979a78765c7a.js from UglifyJs
Unexpected token: name (features)
[./node_modules/pica/lib/mathlib.js:19,0][static/js/vendor.6ccd9e38979a78765c7a.js:39003,6]
I read that the new beta version of the Webpack plugin supports ES6:
https://github.com/webpack-contrib/uglifyjs-webpack-plugin
new webpack.optimize.UglifyJsPlugin({
uglifyOptions: {
ie8: false,
ecma: 8, // I also tried 7 and 6
parse: {},
mangle: {
properties: {
// mangle property options
}
},
output: {
comments: false,
beautify: false
},
compress: {},
warnings: true
}
}),
However, now I get another error:
ERROR in static/js/vendor.6ccd9e38979a78765c7a.js from UglifyJs
Unexpected token: name (features)
[static/js/vendor.6ccd9e38979a78765c7a.js:39003,6]
What could be the problem?
You can try installing babel-preset-env and adding presets": [ "env" ] to your webpack.config.js or babelrc.
Uglify cannot parse ES6 on its own( as far as I know), so you need to transpile your code down to ES5, post-processing your generated JS with babel, or use a different minifier. My recommendation is Babelify to which I switched after having constant errors with Uglify.
Edit: The problem might be in your new webpack.optimize.UglifyJsPlugin declaration, There are problems with using this declaration with Webpack 3+. You need to import the uglifyjs-webpack-plugin and change plugin declaration to new UglifyJSPlugin(example). Here is a reference.
Example:
const UglifyJSPlugin = require('uglifyjs-webpack-plugin')
const config = {
...
plugins: [
new UglifyJSPlugin({ uglifyOptions: { ...options } })
]
}
For anyone arriving here stuck for various reasons on webpack3 and webpack.optimize.UglifyJsPlugin:
For us, the answer was to let our webpack babel-loader transpile those node_modules. Instead of sending all node_modules through the babel loader (which isn't recommended for performance reasons), you can exclude all apart from specific packages and package name patterns like this:
exclude: /node_modules\/(?!(react-markdown|mdast-util-.*|micromark-.*)\/).*/

How to import remote AMD modules with Angular2+WebPack?

In my Angular2+TypeScript+WebPack project I would like to use / import the Esri ArcGIS JavaScript API from the following url:
https://js.arcgis.com/4.1/
so that a import Map from 'esri/Map'; would import the following module
https://js.arcgis.com/4.1/esri/Map.js
I've seen people are typically using systemjs to load these modules, however, I would prefer not to use systemjs. Is there a way to do that with webpack only?
webpack.config:
var webpack = require("webpack");
module.exports = {
entry: {
'polyfills': './app/polyfills.js',
'vendor': './app/vendor.js',
'app': './app/boot.js'
},
output: {
path: __dirname,
filename: "./prod/[name].js"
},
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: ['app', 'vendor', 'polyfills']
}),
new webpack.optimize.UglifyJsPlugin({
beautify: false,
mangle: {screw_ie8: true, keep_fnames: true},
compress: {screw_ie8: true},
comments: false
})
]
};
webpack is a module bundler not a javascript loader. It package files from local disk and don't load files from the web (except its own chunks). Use a javascript loader, i.e. script.js
https://github.com/webpack/webpack/issues/150#issuecomment-32756166
It's also worth noting you won't be able to load a module from an external source and then import using es6 syntax as the remote module needs to go through Webpack. You'll have to make sure you fetch a UMD / global script.
I followed this sample and was able to load dojo, webpack, typescript and angular 2.
Just be patient, is still hellish with imports eg:
import * as Map from 'esri/Map';
import * as Point from 'esri/geometry/Point';
import * as Circle from 'esri/geometry/Circle';

How to bundle vendor scripts separately and require them as needed with Webpack?

I'm trying to do something that I believe should be possible, but I really can't understand how to do it just from the webpack documentation.
I am writing a JavaScript library with several modules that may or not depend on each other. On top of that, jQuery is used by all modules and some of them may need jQuery plugins. This library will then be used on several different websites which may require some or all modules.
Defining the dependencies between my modules was very easy, but defining their third-party dependencies seems to be harder then I expected.
What I would like to achieve: for each app I want to have two bundle files one with the necessary third-party dependencies and other with the necessary modules from my library.
Example:
Let's imagine that my library has the following modules:
a (requires: jquery, jquery.plugin1)
b (requires: jquery, a)
c (requires: jquery, jquery.ui, a, b)
d (requires: jquery, jquery.plugin2, a)
And I have an app (see it as a unique entry file) that requires modules a, b and c. Webpack for this case should generate the following files:
vendor bundle: with jquery, jquery.plugin1 and jquery.ui;
website bundle: with modules a, b and c;
In the end, I would prefer to have jQuery as a global so I don't need to require it on every single file (I could require it only on the main file, for example). And jQuery plugins would just extend the $ global in case they are required (it is not a problem if they are available to other modules that don't need them).
Assuming this is possible, what would be an example of a webpack configuration file for this case? I tried several combinations of loaders, externals, and plugins on my configuration file, but I don't really get what they are doing and which ones should I use. Thank you!
in my webpack.config.js (Version 1,2,3) file, I have
function isExternal(module) {
var context = module.context;
if (typeof context !== 'string') {
return false;
}
return context.indexOf('node_modules') !== -1;
}
in my plugins array
plugins: [
new CommonsChunkPlugin({
name: 'vendors',
minChunks: function(module) {
return isExternal(module);
}
}),
// Other plugins
]
Now I have a file that only adds 3rd party libs to one file as required.
If you want get more granular where you separate your vendors and entry point files:
plugins: [
new CommonsChunkPlugin({
name: 'common',
minChunks: function(module, count) {
return !isExternal(module) && count >= 2; // adjustable
}
}),
new CommonsChunkPlugin({
name: 'vendors',
chunks: ['common'],
// or if you have an key value object for your entries
// chunks: Object.keys(entry).concat('common')
minChunks: function(module) {
return isExternal(module);
}
})
]
Note that the order of the plugins matters a lot.
Also, this is going to change in version 4. When that's official, I update this answer.
Update: indexOf search change for windows users
I am not sure if I fully understand your problem but since I had similar issue recently I will try to help you out.
Vendor bundle.
You should use CommonsChunkPlugin for that. in the configuration you specify the name of the chunk (e.g. vendor), and file name that will be generated (vendor.js).
new webpack.optimize.CommonsChunkPlugin("vendor", "vendor.js", Infinity),
Now important part, you have to now specify what does it mean vendor library and you do that in an entry section. One one more item to entry list with the same name as the name of the newly declared chunk (i.e. 'vendor' in this case). The value of that entry should be the list of all the modules that you want to move to vendor bundle.
in your case it should look something like:
entry: {
app: 'entry.js',
vendor: ['jquery', 'jquery.plugin1']
}
JQuery as global
Had the same problem and solved it with ProvidePlugin. here you are not defining global object but kind of shurtcuts to modules. i.e. you can configure it like that:
new webpack.ProvidePlugin({
$: "jquery"
})
And now you can just use $ anywhere in your code - webpack will automatically convert that to
require('jquery')
I hope it helped. you can also look at my webpack configuration file that is here
I love webpack, but I agree that the documentation is not the nicest one in the world... but hey.. people were saying same thing about Angular documentation in the begining :)
Edit:
To have entrypoint-specific vendor chunks just use CommonsChunkPlugins multiple times:
new webpack.optimize.CommonsChunkPlugin("vendor-page1", "vendor-page1.js", Infinity),
new webpack.optimize.CommonsChunkPlugin("vendor-page2", "vendor-page2.js", Infinity),
and then declare different extenral libraries for different files:
entry: {
page1: ['entry.js'],
page2: ['entry2.js'],
"vendor-page1": [
'lodash'
],
"vendor-page2": [
'jquery'
]
},
If some libraries are overlapping (and for most of them) between entry points then you can extract them to common file using same plugin just with different configuration. See this example.
In case you're interested in bundling automatically your scripts separately from vendors ones:
var webpack = require('webpack'),
pkg = require('./package.json'), //loads npm config file
html = require('html-webpack-plugin');
module.exports = {
context : __dirname + '/app',
entry : {
app : __dirname + '/app/index.js',
vendor : Object.keys(pkg.dependencies) //get npm vendors deps from config
},
output : {
path : __dirname + '/dist',
filename : 'app.min-[hash:6].js'
},
plugins: [
//Finally add this line to bundle the vendor code separately
new webpack.optimize.CommonsChunkPlugin('vendor', 'vendor.min-[hash:6].js'),
new html({template : __dirname + '/app/index.html'})
]
};
You can read more about this feature in official documentation.
Also not sure if I fully understand your case, but here is config snippet to create separate vendor chunks for each of your bundles:
entry: {
bundle1: './build/bundles/bundle1.js',
bundle2: './build/bundles/bundle2.js',
'vendor-bundle1': [
'react',
'react-router'
],
'vendor-bundle2': [
'react',
'react-router',
'flummox',
'immutable'
]
},
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor-bundle1',
chunks: ['bundle1'],
filename: 'vendor-bundle1.js',
minChunks: Infinity
}),
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor-bundle2',
chunks: ['bundle2'],
filename: 'vendor-bundle2-whatever.js',
minChunks: Infinity
}),
]
And link to CommonsChunkPlugin docs: http://webpack.github.io/docs/list-of-plugins.html#commonschunkplugin

Minimize only one Webpack chunk

I want to minimize my Javascript code for production. However I don't want to minimize the vendors' code because they already have a minimize version.
My current webpack.config.js splits the output code in two chunks.
module.exports = {
entry: {
vendor: ['jquery','angular'],
app: ['./Client/app.start.js']
},
output: {
filename: 'bundle.js',
path: __dirname
},
resolve : {
alias : {
'angular' : 'angular/angular.min.js',
'jquery' : 'jquery/dist/jquery.min.js'
}
},
plugins: [
new webpack.optimize.CommonsChunkPlugin("vendor", "vendor.bundle.js"),
new webpack.optimize.UglifyJsPlugin({minimize: true})
]
}
When I run >> webpack, both chunks ("bundle.js" and "vendor.bundle.js") are minimized. How can I configure Webpack to only minimize "bundle.js"?
Thanks
If, for some reason, you really want to minify only one bundle you could use the test option of the UglifyJsPlugin. Use the bundle name and don't test for individual modules with a regex because when the code is consumed by UglifyJsPlugin it's already bundled.
new webpack.optimize.UglifyJsPlugin({
compress: { warnings: false },
test: /(vendor.bundle.js\.js)+/i
})
See the docs: https://webpack.github.io/docs/list-of-plugins.html#uglifyjsplugin
test, include, exclude (RegExp|Array): configure filtering of processed files (default: test: /.js($|\?)/i)
Usually, you would have different configs (one with the uglify and another without), for production and development, you would minimize only in production, if that's what you want.
You probably already know this, but is good to be thorough. What you may not know, is that webpack does the job better and it is recommended to use untouched code and let webpack do its thing. I don't believe that the uglifyJsPlugin is able to target chunks, maybe there is another plugin that could do it, but i am unaware.
As a side note, you don't have to worry about double minification, it adds a small effect, considering that it is a production environment and that it doesn't change by the minute.
This can be achieved via a hack as you can see in below code in webpack.config.js file:
`
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
const PRODUCTION = process.env.NODE_ENV === 'production';
const plugins = [
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor', filename: './assets/js/vendor.min.js',
minChunks: Infinity
}),
new webpack.DefinePlugin({
'process.env': {
'NODE_ENV': JSON.stringify(process.env.NODE_ENV)
}
}),
(...etc other plugins)
];
if (PRODUCTION) {
plugins.push(new UglifyJsPlugin({
include: /\.min\.js$/
}));
}
`
Check if you are creating production build.
Then, you can name the chunks you want to create as minified with".min.js" extension.
Rest unglifyjsplugin -> include filter will ensure that only these chuks will be minified.
Here, in this case, it'll only minify the vendor.min.js file.

Categories