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 importsog 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.jsog module.js.

Inde i module.jsdig eksporterer du 2 navngivne pilefunktioner:

// module.js export const sayHello = name => `Hello ${name}!`; export const sayBye = name => `Bye ${name}!`

Kun sayHelloimporteres til index.jsfil:

// index.js import { sayHello } from './module'; sayHello('World');

sayByeeksporteres, 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 Liutikov

Træ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 npmprojekt:

npm init -y

Den -yvalgmulighed genererer package.jsonhurtigt 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 webpacknyeste kommando. Tjek hvordan man downloader?sektion af webpacks seneste udgivelsesindlæg for flere detaljer.

Åbn package.jsonog 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 srcmappe med 2 filer inde i dit projekts rod :

mkdir src && cd src touch index.js touch module.js

Bemærk: Den touchkommando 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 entryfil og en outputfil via CLI. Disse oplysninger fortæller webpack, "gå til src/index.jsog 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 distmappe, 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-loaderhow 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!