Skip to content

Instantly share code, notes, and snippets.

@sokra
Last active November 7, 2023 23:23
Show Gist options
  • Save sokra/27b24881210b56bbaff7 to your computer and use it in GitHub Desktop.
Save sokra/27b24881210b56bbaff7 to your computer and use it in GitHub Desktop.
What's new in webpack 2

What's new in webpack 2

Work in progress... This reflects stuff until 2.0.5-beta+

Major changes

ES6 Modules

webpack 2 brings native support ES6 Modules. This means webpack now understands import and export without them being transformed to CommonJS:

import { currentPage, readPage } from "./book";

currentPage === 0;
readPage();
currentPage === 1;
// book.js
export var currentPage = 0;

export function readPage() {
	currentPage++;
}

export default "This is a book";

Code Splitting with ES6

The ES6 Loader spec defines System.import as method to load ES6 Modules dynamically on runtime.

Webpack threads System.import as splitpoint and puts the requested module in a separate chunk.

System.import takes the module name as argument and returns a Promise.

function onClick() {
	System.import("./module").then(module => {
		module.default;
	}).catch(err => {
		console.log("Chunk loading failed");
	});
}

Good news: Failure to load a chunk can be handled now.

Dynamic expressions

It's possible to pass an partial expression to System.import. This is handled similar to expressions in CommonJS (webpack creates a context with all possible files).

System.import creates a separate chunk for each possible module.

function route(path, query) {
	return System.import("./routes/" + path + "/route")
		.then(route => new route.Route(query));
}
// This creates a separate chunk for each possible route

Mixing ES6 with AMD and CommonJS

As for AMD and CommonJS you can freely mix all three module types (even within the same file). Webpack behaves similar to babel in this case:

// CommonJS consuming ES6 Module
var book = require("./book");

book.currentPage;
book.readPage();
book.default === "This is a book";
// ES6 Module consuming CommonJS
import fs from "fs"; // module.exports map to default
import { readFileSync } from "fs"; // named exports are read from returned object+

typeof fs.readFileSync === "function";
typeof readFileSync === "function";

babel and webpack

The es2015 babel preset transforms ES6 Modules to CommonJS by default. To use webpack to process ES6 Modules you should use the es2015-webpack preset instead.

ES6 specific optimizations

The static nature of ES6 Modules allows some new kind of optimizations. In example in many cases it's possible to detect which exports are used and which aren't used.

In cases in which webpack can say for sure that an export isn't used it omits the statement which exposes the export to other modules. Later the minimizer may flag the declaration as unused and omits it.

In the following cases it's possible to detect usage:

  • named import
  • default import
  • reexport

In the following cases it's not possible to detect usage:

  • import * as ... when used indirectly
  • CommonJS or AMD consuming ES6 module
  • System.import

ES6 export mangling

In cases where it's possible to track export usage, webpack can mangle export names to single char properties.

Configuration

In the past environment variables are often used to handle different environments in the configuration file. Webpack 2 brings a new way to pass options to the configuration.

The configuration file can export a function which returns the configuration. The function is called by the CLI and the value passed via --env is passed to the configuration function.

You can pass a string (--env dev => "dev") or a complex options object (--env.minimize --env.server localhost => {minimize: true, server: "localhost"}). I would recommend using an object, because it's more extendable, but it's up to you.

Example

// webpack.config.babel.js
exports default function(options) {
	return {
		// ...
		devtool: options.dev ? "cheap-module-eval-source-map" : "hidden-source-map"
	};
}

Resolving options

There was a major refactoring in the resolver (https://github.com/webpack/enhanced-resolve). This means the resolving option were changed too. Mostly simplification and changes that make it more unlikely to configure it incorrectly.

The new options are:

{
	modules: [path.resolve(__dirname, "app"), "node_modules"]
	// (was split into `root`, `modulesDirectories` and `fallback` in the old options)
	// In which folders the resolver look for modules
	// relative paths are looked up in every parent folder (like node_modules)
	// absolute paths are looked up directly
	// the order is respected

	descriptionFiles: ["package.json", "bower.json"],
	// These JSON files are read in directories

	mainFields: ["main", "browser"],
	// These fields in the description files are looked up when trying to resolve the package directory

	mainFiles: ["index"]
	// These files are tried when trying to resolve a directory

	aliasFields: ["browser"],
	// These fields in the description files offer aliasing in this package
	// The content of these fields is an object where requests to a key are mapped to the corresponding value

	extensions: [".js", ".json"],
	// These extensions are tried when resolving a file

	enforceExtension: false,
	// If false it will also try to use no extension from above

	moduleExtensions: ["-loader"],
	// These extensions are tried when resolving a module

	enforceModuleExtension: false,
	// If false it's also try to use no module extension from above

	alias: {
		jquery: path.resolve(__dirname, "vendor/jquery-2.0.0.js")
	}
	// These aliasing is used when trying to resolve a module
}

Minor breaking changes

Promise polyfill

The chunk loading stuff now relies on Promise being available. This means you need to provide a Promise polyfill for older browsers.

The ES6 spec uses promises and I don't want to include a Promise polyfill in every bundle. So it's up the application developer to provide the polyfill if needed.

Can I use Promises?

Other polyfills

You need a Object.defineProperty polyfill for ES6 Module or if using the module object in other ways than module.exports, module.id, module.loaded or module.hot.

For ES6 Modules you also need a Function.prototype.bind polyfill.

That's not new but anyway: You need an Object.keys polyfill for require.context().keys().

Loaders configuration

The loaders in the configuration now match to the resourcePath instead of the resource. This means the query string is no longer included for matching.

This was an issue with bootstrap which complicates the test for bootstrap fonts and images from /\.svg$/ to /\.svg($|\?)/. Now you can use the simple form.

The loader in the configuration now resolves relative to the configuration file (or the context option in the configuration file if specified). This should fix some issues with npm linked modules that are outside of the current package.

Another change allows the following syntax to configure loaders:

loaders: [
	{
		test: /\.css$/,
		loaders: [
			"style-loader",
			{ loader: "css-loader", query: { modules: true } },
			{
				loader: "sass-loader",
				query: {
					includePaths: [
						path.resolve(__dirname, "some-folder")
					]
				}
			}
		]
	}
]

Loader options & minimize

The UglifyJsPlugin no longer puts loaders into minimize mode. The debug option has been removed. Loaders should no longer read their options from the webpack configuration. Instead you need to provide these options with the LoaderOptionsPlugin.

new webpack.LoaderOptionsPlugin({
	test: /\.css$/, // optionally pass test, include and exclude, default affects all loaders
	minimize: true,
	debug: false,
	options: {
		// pass stuff to the loader
	}
})

This happens for separation of concern reasons. I want to disallow arbitrary keys in the configuration, to enable configuration validation.

Plugins

Many plugins now take option objects instead of multiple arguments. This happens because it is easier to extend. They throw an Error when the old argument style is passed.

HMR communication

In webpack 1 the update signal used the Web Messaging API (postMessage). Webpack 2 uses a standard event emitter to receive the event. This means WebSocket must be inline in the bundle.

webpack-dev-server has inlined mode as default now.

This should allow to use the webpack-dev-server to update code in WebWorkers.

Occurrence order

The plugin is no longer needed and occurrence order is on by default.

Code Splitting

require.ensure and AMD require is now always async, even if the chunk was already loaded.

@choonchernlim
Copy link

@boo1ean ... regarding preloaders, try this:-

// webpack 1.x
module: {
    preLoaders: [
      {
        test: /\.js?$/,
        loader: 'eslint',
        exclude: /node_modules/
      }
    ]
}

... becomes ...

// webpack 2.x
module: {
    rules: [
      {
        enforce: 'pre',
        test: /\.js?$/,
        loader: 'eslint',
        exclude: /node_modules/
      }
    ]
}

@faller
Copy link

faller commented Sep 23, 2016

Nice job! How's about i18n-webpack-plugin?

Copy link

ghost commented Oct 4, 2016

just got my Angular 2.0 stuff moved over to webpack 2. Got tripped up on CSS/SASS sourcemaps pretty good however. Anybody have any idea how to get that working with beta.25??

{ test : /\.scss$/, include : [ helpers.root('src/app/sass-config.scss'), helpers.root('src/app/styles') ], loader : ExtractTextPlugin.extract({ loader : [ { loader : 'css', query : { sourceMap : false // set to true... } }, 'postcss', { loader : 'sass', query : { sourceMap : false // set to true... } } ], fallbackLoader : 'style-loader' }) }

pretty sure I nailed the config...and by "pretty sure" I mean "I have no idea" >.<

@jasan-s
Copy link

jasan-s commented Oct 29, 2016

I can't seem to figure out how to convert this to webpack 2 :

{ test: /\.css$/, loader: ExtractTextPlugin.extract('style', 'css?sourceMap&modules&localIdentName=name]__[local]___[hash:base64:5]&importLoader=1!postcss') }, with

postcss: [ autoprefixer({ browsers: ['last 2 versions'] }) ]

@rxgx
Copy link

rxgx commented Nov 3, 2016

@monochrome-yeh
Copy link

@rbartoli
Copy link

Is ES2015 in webpack.config.js still supported? I used to append a .babel before the extension, but it seems it's not supported anymore.

@rxgx
Copy link

rxgx commented Nov 16, 2016

@rbartoli Are you referring to the Babel preset es2015? There's been some recent improvements to Babel and that preset for working with ES2015+ features.

@layton-glympse
Copy link

This is great and all, but locking the config to a schema just completely broke our deployment procedures.
Where we were appending a metadata key to the config, it is now invalid, which means I can't merge metadata (env-specific data) across configs, making my common config much less "common". Now I have to abstract that stuff out into some common-vars file, or something?
Do you have a solution for this? I understand wanting a validatable schema, but at least give us a key on the config object for misc other config stuff, it's a pretty common scenario. Because the loaderConfigPlugin seems like a very hacky way to try to somehow access commonconfig settings that aren't part of the base schema.
Thoughts?

@augbog
Copy link

augbog commented Nov 28, 2016

I like how this gist is marked Secret but its one of the top Google search results for what's new in webpack 2 lol

@drewdeal
Copy link

drewdeal commented Jan 9, 2017

Looking forward to using WP2 official tree-shaking, but shouldn't 1.current already only put functions in the bundle that I have import or at least only the ones I export? I have been experimenting with this thinking that it should work if I do something a certain way, as I see references to rxjs and other libraries constantly saying something along the lines of, "only import the methods you intend to use to reduce bundle size".

@pavelthq
Copy link

People, is there any ready to use forks or repos where used workable babel-polyfills for IE11? Chunked with vendor and bundle with CommonChunksPlugin and of course work with react? I have tried many different solutions but it looks like it doesn't work

@ariesshrimp
Copy link

ariesshrimp commented Jan 26, 2017

@rxgx, @rbartoli wants to know if this has changed:

// webpack.config.babel.js <--- special naming convention makes ES2015 features magically work inside the webpack.config file
// don't need to run $ babel-node node_modules/bin/webpack 
// to make this file work.
// just run $ webpack
import webpack from 'webpack' 

export default {
  modules:{},
  plugins: ...require('./plugins'), // <--- spread operator works
  // ... other stuff
}

@adyz
Copy link

adyz commented Feb 24, 2017

What if I want to run webpack from my node_modules local folder? it seems like it cannot resolve my required modules. Here is's how my resolve webpack looks:

resolve: {
    modules: [
        path.resolve(__dirname, ""),
        path.join(__dirname, "node_modules/")
    ],
    descriptionFiles: ['package.json'],
    extensions : ['*','.js', '.hbs', '.vue'],
    aliasFields: ["browser"],
    alias: {
        'handlebars': 'handlebars/runtime.js',
        'vue$': 'vue/dist/vue.common.js'
    },
}

@jinhduong
Copy link

jinhduong commented Apr 17, 2017

Big thanks to Webpack team ;).

@ChrisKramer2020
Copy link

ChrisKramer2020 commented Aug 6, 2017

Can someone help me? I am having trouble starting my localhost server here is a link to my problem on stack overflow here

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment