Trærystende ES6-moduler i webpakke 2

Webpack 2 blev netop frigivet fra beta i sidste uge. Det bringer med sig en række forventede funktioner, herunder native support til ES6-moduler.
I stedet for at bruge var module = require('module')
syntaksen understøtter webpack 2 ES6 imports
og exports
. Dette åbner døren for kodeoptimeringer som trærystning .
Hvad er trærystning?
Populariseret af Rich Harris' Rollup.js modul bundler, træ-ryster er evnen til kun indeholde kode i dit bundt, der bliver brugt.
Da jeg først spillede rundt med Rollup, blev jeg forbløffet over, hvor godt det fungerede med ES6-moduler. Udviklingsoplevelsen følte bare ... rigtigt. Jeg kunne oprette separate moduler skrevet i "fremtidig JavaScript" og derefter inkludere dem hvor som helst i min kode. Enhver kode, der bliver ubrugt, gør det ikke til min pakke. Geni!
Hvilket problem løser det?
Hvis du skriver JavaScript i 2017 og forstår de forskellige værktøjer (se: JavaScript-træthed), føles din udviklingsoplevelse sandsynligvis ret flydende. Dette er vigtigt, men hvad der også er vigtigt er brugeroplevelse . Mange af disse moderne værktøjer ender oppustede webapplikationer med massive JavaScript-filer, hvilket resulterer i langsommere ydeevne.
Hvad jeg elsker ved Rollup er, at det tager et skub i dette spørgsmål og bringer en løsning i spidsen for JavaScript-samfundet. Nu forsøger store navne som webpack at gentage det.
Trærystelser er måske ikke ”løsningen til at afslutte alle løsninger”, men det er en vigtig del af den større tærte.
Et simpelt eksempel
Inden du kommer i gang, vil jeg give dig et trivielt eksempel på trærystning. Din ansøgning består af 2 filer index.js
og module.js
.
Inde i module.js
dig eksporterer du 2 navngivne pilefunktioner:
// module.js export const sayHello = name => `Hello ${name}!`; export const sayBye = name => `Bye ${name}!`
Kun sayHello
importeres til index.js
fil:
// index.js import { sayHello } from './module'; sayHello('World');
sayBye
eksporteres, men aldrig importeres. Overalt. Derfor er det ikke på grund af trærystning inkluderet i din pakke:
// bundle.js const sayHello = name => `Hello ${name}!`; sayHello('World');
Afhængigt af den anvendte bundler kan outputfilen ovenfor se anderledes ud. Det er bare en forenklet version, men du får ideen.
For nylig læste jeg en artikel skrevet af Roman Liutikov, og han lavede en god analogi for at visualisere begrebet trærystning:
”Hvis du spekulerer på, hvorfor det kaldes trærystning: tænk på din applikation som en afhængighedsgraf, dette er et træ, og hver eksport er en gren. Så hvis du ryster træet, vil de døde grene falde. ” - Roman LiutikovTrærystelser i webpack 2
Desværre for dem af os, der bruger webpack, er trærystning "bag en switch", hvis du vil. I modsætning til Rollup skal der konfigureres en vis konfiguration, før du kan få den funktionalitet, du leder efter. "Bag en switch" -del kan forvirre nogle mennesker. Jeg forklarer.
Trin 1: Projektopsætning
Jeg antager, at du forstår det grundlæggende i webpack og kan finde vej rundt i en grundlæggende webpack-konfigurationsfil.
Lad os starte med at oprette en ny mappe:
mkdir webpack-tree-shaking && cd webpack-tree-shaking
Når vi er inde, lad os initialisere et nyt npm
projekt:
npm init -y
Den -y
valgmulighed genererer package.json
hurtigt uden at du skal svare på en masse spørgsmål.
Lad os derefter installere et par projektafhængigheder:
npm i --save-dev [email protected] html-webpack-plugin
Kommandoen ovenfor installerer den nyeste betaversion af webpack 2 lokalt i dit projekt samt et nyttigt plugin med navnet html-webpack-plugin
. Sidstnævnte er ikke nødvendigt for målet med denne gennemgang, men vil gøre tingene lidt hurtigere.
Bemærk : Kommandoen npm i --save-dev [email protected]
anbefales stadig af webpack-teamet i skrivende stund. [email protected]
vil til sidst blive faset ud til fordel for den webpack
nyeste kommando. Tjek hvordan man downloader?sektion af webpacks seneste udgivelsesindlæg for flere detaljer.
Åbn package.json
og sørg for, at de er installeret som devDependencies
.
Trin 2: Opret JS-filer
For at se trærystning i aktion skal du have noget JavaScript at lege med. Opret en src
mappe med 2 filer inde i dit projekts rod :
mkdir src && cd src touch index.js touch module.js
Bemærk: Den touch
kommando opretter en ny fil gennem terminalen.
Kopier koden nedenfor til de rigtige filer:
// 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);
Hvis du er kommet så langt, skal din mappestruktur se sådan ud:
/ | - node_modules/ | - src/ | | - index.js | | - module.js | - package.json
Trin 3: Webpack fra CLI
Da du ikke har oprettet nogen konfigurationsfil til dit projekt, er den eneste måde at få webpack til at udføre noget arbejde i øjeblikket via webpack CLI. Lad os udføre en hurtig test.
I din terminal skal du køre følgende kommando i dit projekts rod:
node_modules/.bin/webpack
Efter at have kørt denne kommando, skal du se output som dette:
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.
Kommandoen gør ikke noget, og webpack CLI bekræfter dette. Du har ikke givet webpack nogen oplysninger om, hvilke filer du vil samle. Du kan give disse oplysninger via kommandolinjen eller en konfigurationsfil. Lad os vælge den førstnævnte bare for at teste, at alt fungerer:
node_modules/.bin/webpack src/index.js dist/bundle.js
Hvad du har gjort nu er at sende webpack en entry
fil og en output
fil via CLI. Disse oplysninger fortæller webpack, "gå til src/index.js
og bundt al den nødvendige kode i dist/bundle.js
". Og det gør netop det. Du vil bemærke, at du nu har en dist
mappe, der indeholder bundle.js
.
Åbn den og tjek den ud. Der er noget ekstra javascript i bundtet, der er nødvendigt for, at webpack kan gøre sine ting, men i bunden af filen skal du også se din egen kode.
Trin 4: Opret en webpack-konfigurationsfil
Webpack kan håndtere mange ting. Jeg har brugt en god del af min fritid på at dykke ned i denne bundler, og jeg har stadig næppe ridset overfladen. Når du har flyttet passerede trivielle eksempler, er det bedst at efterlade CLI og oprette en konfigurationsfil til håndtering af tunge løft.
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! Du har nu en fungerende implementering af trærystning i webpack 2!
For de nysgerrige har jeg langsomt gentaget mig over min egen lette webpack-konfiguration i en GitHub Repo:
jake-wies / webpack-hotplate
webpack-hotplate - En webpack-kedelplade til personlige projekter
github.com
Det er ikke beregnet til at være alt for detaljeret og oppustet. Det er fokuseret på at være en tilgængelig kogeplade med gennemgange ved hver tur. Hvis du er interesseret, så tjek det ud!
Hvis du har spørgsmål, er du velkommen til at kontakte Twitter!