
A Webpack 2 éppen a múlt héten jelent meg a bétaverzióból. Számos várt funkciót hoz magával, beleértve az ES6 modulok natív támogatását.
A var module = require('module')
szintaxis helyett a 2-es webcsomag támogatja az ES6 imports
és az ES6-ot exports
. Ez megnyitja a kaput a kódoptimalizáláshoz, mint például a fa remegése .
Mi az a fa-remegés?
A Rich Harris Rollup.js modulcsomagolója népszerűsítette, a fa megrázása az a képesség, hogy csak kódot tartalmazzon a használt csomagban .
Amikor először játszottam a Rollup-tal, csodálkoztam, hogy milyen jól működik ez az ES6 modulokkal. A fejlesztési tapasztalatok csak… úgy érezték. Létrehozhatnék külön modulokat, amelyek „jövőbeli JavaScript” -be vannak írva, majd bárhova belefoglalhatnám a kódomba. Bármely használaton kívüli kód nem kerül a csomagomba. Zseni!
Milyen problémát old meg?
Ha 2017-ben JavaScriptet ír, és megérti (lásd: JavaScript fáradtság) a körülötte lévő különféle eszközöket, akkor a fejlesztési tapasztalatai valószínűleg elég folyékonynak hatnak. Ez fontos, de ami szintén fontos, az a felhasználói élmény . Ezek közül a modern eszközök közül sok a hatalmas JavaScript fájlokkal duzzadó webalkalmazásokat eredményez, ami lassabb teljesítményt eredményez.
Amit a Rollup-ban szeretek, az az, hogy szúrja ezt a kérdést, és megoldást hoz a JavaScript-közösség előterébe. Most olyan nagy nevek próbálnak iterálni, mint a webpack.
A fa megrázása nem biztos, hogy „megoldás az összes megoldás megszüntetésére”, de fontos darab a nagyobb tortában.
Egy egyszerű példa
Mielőtt elkezdené, szeretnék egy apró példát adni a fa rázására. Az alkalmazás 2 fájlból áll, index.js
és module.js
.
Belül module.js
2 megnevezett nyílfunkciót exportál:
// module.js export const sayHello = name => `Hello ${name}!`; export const sayBye = name => `Bye ${name}!`
Csak sayHello
a index.js
fájlba importálódik :
// index.js import { sayHello } from './module'; sayHello('World');
sayBye
exportálják, de soha nem importálják. Bárhol. Ezért a fa rázkódása miatt nem lesz benne a csomagban:
// bundle.js const sayHello = name => `Hello ${name}!`; sayHello('World');
A felhasznált csomagtól függően a fenti kimeneti fájl másképp nézhet ki. Ez csak egy egyszerűsített változat, de megkapja az ötletet.
Nemrégiben olvastam egy cikket, amelyet Roman Liutikov írt, és nagyszerű hasonlatot tett a fa rázás fogalmának vizualizálása érdekében:
„Ha kíváncsi arra, miért hívják azt fa megrázásának: gondolja az alkalmazását függőségi grafikonként, ez egy fa, és minden export egy ág. Tehát ha megrázza a fát, az elhalt ágak leesnek. - Roman LiutikovFa megrázása a 2. webpackben
Sajnos a webpack-ot használók számára a fa megrázása “kapcsoló mögött van”, ha akarja. Az összesítéssel ellentétben néhány konfigurációt el kell végezni, mielőtt megszerezheti a keresett funkciót. A „kapcsoló mögött” rész megzavarhatja egyes embereket. Elmagyarázom.
1. lépés: Projekt beállítása
Feltételezem, hogy megértette a webpack alapjait, és eligazodik egy alapvető webpack konfigurációs fájlban.
Kezdjük egy új könyvtár létrehozásával:
mkdir webpack-tree-shaking && cd webpack-tree-shaking
Miután beléptünk, inicializálunk egy új npm
projektet:
npm init -y
Az -y
opció package.json
gyorsan létrehozza, anélkül, hogy megválaszolna egy csomó kérdést.
Ezután telepítsünk néhány projektfüggőséget:
npm i --save-dev webpack@beta html-webpack-plugin
A fenti parancs a webpack 2 legújabb bétaverzióját helyben telepíti a projektbe, valamint egy hasznos plugint html-webpack-plugin
. Ez utóbbi nem szükséges az áttekintés céljához, de kissé gyorsabbá teszi a dolgot.
Megjegyzés : A parancsot npm i --save-dev webpack@beta
a webpack csapata az írás idején továbbra is javasolja. webpack@beta
végül a webpack
legújabb parancs javára szüntetik meg . Nézze meg a Hogyan töltsem le?rész a webpack legújabb kiadási posztjáról további részletekért.
Nyisd meg, package.json
és győződj meg róla, hogy a (z) néven lettek telepítve devDependencies
.
2. lépés: Hozzon létre JS fájlokat
Ahhoz, hogy a fák rázkódhassanak működésben, rendelkeznie kell egy kis JavaScript-szel, amellyel játszhat. A projekt gyökérkönyvtárában hozzon létre egy src
mappát, amelyben 2 fájl található:
mkdir src && cd src touch index.js touch module.js
Megjegyzés: A touch
parancs új fájlt hoz létre a terminálon keresztül.
Másolja az alábbi kódot a megfelelő fájlokba:
// module.js export const sayHello = name => `Hello ${name}!`; export const sayBye = name => `Bye ${name}!`;
// index.js import { sayHello } from './module'; const element = document.createElement('h1'); element.innerHTML = sayHello('World'); document.body.appendChild(element);
Ha idáig eljutott, akkor a mappaszerkezetnek így kell kinéznie:
/ | - node_modules/ | - src/ | | - index.js | | - module.js | - package.json
3. lépés: Webcsomag a CLI-ből
Mivel a projektjéhez még nem hoztak létre konfigurációs fájlt, a webpack használatának egyetlen módja a webpack CLI-n keresztül van. Végezzünk egy gyors tesztet.
A terminálban futtassa a következő parancsot a projekt gyökérkönyvtárában:
node_modules/.bin/webpack
A parancs futtatása után az alábbi kimenetet kell látnia:
No configuration file found and no output filename configured via CLI option. A configuration file could be named 'webpack.config.js' in the current directory. Use --help to display the CLI options.
A parancs nem tesz semmit, és a webpack CLI ezt megerősíti. Még nem adott meg információt a webcsomagról arról, hogy milyen fájlokat szeretne kötegelni. Ezeket az információkat a parancssoron vagy egy konfigurációs fájlon keresztül adhatja meg. Válasszuk az előbbit csak annak tesztelésére, hogy minden működik:
node_modules/.bin/webpack src/index.js dist/bundle.js
What you’ve done now is pass webpack an entry
file and an output
file via the CLI. This information tells webpack, "go to src/index.js
and bundle up all the necessary code into dist/bundle.js
". And it does just that. You'll notice that you now have a dist
directory containing bundle.js
.
Open it up and check it out. There’s some extra javascript in the bundle necessary for webpack to do its thing, but at the bottom of the file you should see your own code as well.
Step 4: Create a webpack configuration file
Webpack can handle a lot of things. I’ve spent a good chunk of my free time diving into this bundler and I still have barely scratched the surface. Once you’ve move passed trivial examples its best to leave the CLI behind and create a configuration file to handle the heavy lifting.
In your project’s root, create a webpack.config.js
file:
touch webpack.config.js
This file can be as complicated as you make it. We’ll keep it light for the sake of this post:
// webpack.config.js const HtmlWebpackPlugin = require('html-webpack-plugin'); module.exports = { entry: './src/index.js', output: { filename: 'bundle.js', path: 'dist' }, plugins: [ new HtmlWebpackPlugin({ title: 'Tree-shaking' }) ] }
This file provides webpack with the same information you gave to the CLI earlier. You’ve defined index.js
as your entry
file and bundle.js
as your output
file. You've also added your html-webpack-plugin
which will generate an html file in your dist
directory. Convenient.
Go ahead and test this to make sure it’s still working. Remove your dist
directory, and in the command line type:
webpack
If everything went smoothly, you can open up dist/index.html
and see "Hello World!".
Note: The use of a configuration file gives us the convenience of typing webpack
instead of node_modules/.bin/webpack
. Small wins.
Step 5: Babel
I mentioned earlier that webpack 2 brings native support for ES6 modules. This is all true, but it doesn’t change the fact that ES6 is not fully supported across all browsers. Because of this, you’re required to transform your ES6 code into readily acceptable JavaScript using a tool like Babel. In conjunction with webpack, Babel gives us the ability to write your “future JavaScript” without worrying about the implications of unsupported browsers.
Let’s go ahead and install Babel in your project:
npm i --save-dev babel-core babel-loader babel-preset-es2015
Take note of the babel-preset-es2015
package. This little guy is the reason I sat down to write all of this up.
Step 6: babel-loader
Webpack can be configured to transform specific files into modules via loaders. Once they are transformed, they are added to a dependency graph. Webpack uses the graph to resolve dependencies and includes only what is needed into the final bundle. This is the basis for how webpack works.
You can now configure webpack to use babel-loader
to transform all of your .js
files:
// webpack.config.js const HtmlWebpackPlugin = require('html-webpack-plugin'); module.exports = { entry: './src/index.js', output: { filename: 'bundle.js', path: 'dist' }, module: { rules: [ { test: /\.js$/, exclude: /node_modules/, loader: 'babel-loader', options: { presets: [ 'es2015' ] } } ] }, plugins: [ new HtmlWebpackPlugin({ title: 'Tree-shaking' }) ] };
The module
property provides a set of instructions for webpack. It says, "take any files ending in .js
and transform them using babel-loader
, but don't transform any files inside of node_modules
!"
We’re also passing the babel-preset-es2015
package as an option to babel-loader
. This just tells babel-loader
how to transform the JavaScript.
Run webpack
again to make sure everything is good. Yes? Great! What we've done is bundled up your JavaScript files while compiling them down to JavaScript thats readily supported across browsers.
The underlying problem
The package babel-preset-es2015
contains another package named babel-plugin-transform-es2015-modules-commonjs
that turns all of your ES6 modules into CommonJS
modules. This isn't ideal, and here's why.
Javascript bundlers such as webpack and Rollup can only perform tree-shaking on modules that have a static structure. If a module is static, then the bundler can determine its structure at build time, safely removing code that isn’t being imported anywhere.
CommonJS
modules do not have a static structure. Because of this, webpack won’t be able to tree-shake unused code from the final bundle. Luckily, Babel has alleviated this issue by providing developers with an option that you can pass to your presets
array along with babel-preset-es2015
:
options: { presets: [ [ 'es2015', { modules: false } ] ] }
According to Babel’s documentation:
“modules
- Enable transformation of ES6 module syntax to another module type (Enabled by default to "commonjs"). Can be false
to not transform modules, or one of ["amd", "umd", "systemjs", "commonjs"]
".
Slide that extra bit of code into your configuration and you’ll be cooking with peanut oil.
The final state of webpack.config.js
looks like this:
// webpack.config.js const HtmlWebpackPlugin = require('html-webpack-plugin'); module.exports = { entry: './src/index.js', output: { filename: 'bundle.js', path: 'dist' }, module: { rules: [ { test: /\.js$/, exclude: /node_modules/, loader: 'babel-loader', options: { presets: [ [ 'es2015', { modules: false } ] ] } } ] }, plugins: [ new HtmlWebpackPlugin({ title: 'Tree-shaking' }) ] };
The Grand Finale
Run webpack
again and pop open your bundle.js
file. You won't notice any difference. Before you go crazy, know this! It's ok. We've been running webpack in development mode this whole time. Webpack knows that you have unused exports in your code. Even though it's included in the final bundle, sayBye
will never make it to production.
If you still don’t believe me, run webpack -p
in your terminal. The -p
option stands for production. Webpack will perform a few extra performance optimizations, including minification, removing any unused code along the way.
Open up bundle.js
. Since it's minified, go ahead and search for Hello
. It should be there. Search for Bye
. It shouldn't.
Voila! You now have a working implementation of tree-shaking in webpack 2!
For the curious, I’ve been slowly iterating over my own lightweight webpack configuration in a GitHub Repo:
jake-wies/webpack-hotplate
webpack-hotplate - A webpack boilerplate for personal projects
github.com
It’s not meant to be overly verbose and bloated. It’s focused on being an approachable boilerplate with walkthroughs at every turn. If you’re interested, check it out!
If you have any questions, feel free to reach out on Twitter!