Web Bundler CheatSheet, 選擇合適的構建打包工具

題注:Web Bundler CheatSheet 屬於 Awesome-CheatSheet 系列,盤點數個經常使用的開發打包工具清單。歡迎加入阿里南京前端團隊,歡迎關注阿里南京技術專刊瞭解更多訊息。css

Web Bundler CheatSheet | Web 構建與打包工具盤點

工欲善其事,必先利其器,當咱們準備開始某個 Web 相關的項目時,合適的腳手架會讓咱們事半功倍。在 2016-個人前端之路:工具化與工程化一文中,咱們討論了工具化與工程化相關的內容,其中重要的章節就是關於所謂的打包工具。Grunt、Glup 屬於 Task Runner,即任務執行器; 實際上,npm package.json 中定義的腳本也能夠看作 Task Runner,而 Rollup,Parcel 以及 Webpack 則是屬於 Bundler,即打包工具。html

webpack

尺有所短,寸有所長,不一樣的構建工具備其不一樣的適用場景。Webpack 是很是優秀的構建與打包工具,可是其提供了基礎且複雜的功能支持,使得並不適用於所有的場景。Parcel 這樣的零配置打包工具適合於應用型的原型項目構建,而 Rollup 或者 Microbundle 適合於庫的打包,Backpack 則可以幫咱們快速構建 Node.js 項目。筆者在本文中列舉討論的僅是平常工做中會使用的工具,更多的 BrowserifyFusebox 等等構建工具查看 Web 構建與打包工具資料索引或者現代 Web 開發實戰/進階篇前端

Parcel

Parcel 是著名的零配置的應用打包工具,在 TensorflowJS 或者 gh-craft 等算法實驗/遊戲場景構建中,都可以快速地搭建應用。vue

# 安裝 Parcel
$ npm install -g parcel-bundler

# 啓動開發服務器
$ parcel index.html

# 執行線上編譯
$ parcel build index.js

# 指定編譯路徑
$ parcel build index.js -d build/output
複製代碼

Parcel 會爲咱們自動地下載安裝依賴,而且內置了 ES、SCSS 等常見的處理器。在 fe-boilerplate 中提供了 React, React & TypeScript, Vue.js 等 Parcel 常見的示例,這裏以 React 爲例,首先定義組件與渲染:node

// index.js
import React from 'react';
import ReactDOM from 'react-dom';
import logo from '../public/logo.svg';
import './index.css';

const App = () => (
  <div className="App"> <img className="App-Logo" src={logo} alt="React Logo" /> <h1 className="App-Title">Hello Parcel x React</h1> </div> ); ReactDOM.render(<App />, document.getElementById('root')); // Hot Module Replacement if (module.hot) { module.hot.accept(); } 複製代碼

而後定義入口的 index.html 文件:react

<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Parcel React Example</title>

</head>

<body>
  <div id="root"></div>
  <script src="./index.js"></script>
</body>

</html>
複製代碼

而後使用 parcel index.html 運行開發服務器便可。Parcel 中一樣也是支持異步加載的,假設咱們將部分代碼定義在 someModule.js 文件中,而後在用戶真實須要時再進行加載:webpack

// someModule.js
console.log('someModule.js loaded');
module.exports = {
  render: function(element) {
    element.innerHTML = 'You clicked a button';
  }
};
複製代碼

在入口文件中使用 import 進行異步加載:git

console.log('index.js loaded');
window.onload = function() {
  document.querySelector('#bt').addEventListener('click', function(evt) {
    console.log('Button Clicked');
    import('./someModule').then(function(page) {
      page.render(document.querySelector('.holder'));
    });
  });
};
複製代碼

最後值得一提的是,Parcel 內建支持 WebAssembly 與 Rust,經過簡單的 import 導入,便可以使用 WASM 模塊:github

// synchronous import
import {add} from './add.wasm';
console.log(add(2, 3));

// asynchronous import
const {add} = await import('./add.wasm');
console.log(add(2, 3));

// synchronous import
import {add} from './add.rs';
console.log(add(2, 3));

// asynchronous import
const {add} = await import('./add.rs');
console.log(add(2, 3));
複製代碼

這裏 add.rs 是使用 Rust 編寫的簡單加法計算函數:web

#[no_mangle]
pub fn add(a: i32, b: i32) -> i32 {
  return a + b
}
複製代碼

Rollup + Microbundle

Rollup 是較爲爲純粹的模塊打包工具,其相較於 Parcel 與 Webpack 等,更適合於構建 Library,譬如 React、Vue.js、Angular、D三、Moment、Redux 等一系列優秀的庫都是採用 Rollup 進行構建。。Rollup 可以將按照 ESM(ES2015 Module)規範編寫的源碼構建輸出爲 IIFE、AMD、CommonJS、UMD、ESM 等多種格式,而且其較早地支持 Tree Shaking,Scope Hoisting 等優化特性,保證模塊的簡潔與高效。這裏咱們使用的 Rollup 示例配置項目存放在了 fe-boilerplate/rollup。最簡單的 rollup.config.js 文件配置以下:

export default {
  // 指定模塊入口
  entry: 'src/scripts/main.js',
  // 指定包體文件名
  dest: 'build/js/main.min.js',
  // 指定文件格式
  format: 'iife',
  // 指定 SourceMap 格式
  sourceMap: 'inline'
};
複製代碼

若是咱們只是對簡單的 sayHello 函數進行打包,那麼輸出的文件中也只是會簡單地鏈接與調用,而且清除未真實使用的模塊:

(function() {
 'use strict';
  ...
  function sayHelloTo(name) {
    ...
  }
  ...
  const result1 = sayHelloTo('Jason');
  ...
})();
//# sourceMappingURL=data:application/json;charset=utf-8;base64,...
複製代碼

Rollup 一樣具備豐富的插件系統,在 fe-boilerplate/rollup 中咱們也引入了常見的別名、ESLint、環境變量定義、包體壓縮與分析等插件。這裏咱們以最經常使用的 Babel 與 TypeScript 爲例,若是咱們須要在項目中引入 Babel,則一樣在根目錄配置 .babelrc 文件,而後引入 rollup-plugin-babel 插件便可:

import { rollup } from 'rollup';
import babel from 'rollup-plugin-babel';

rollup({
  entry: 'main.js',
  plugins: [
    babel({
      exclude: 'node_modules/**'
    })
  ]
}).then(...)
複製代碼

對於 TypeScript 則是引入 rollup-plugin-typescript 插件:

import typescript from 'rollup-plugin-typescript';

export default {
  entry: './main.ts',

  plugins: [typescript()]
};
複製代碼

Microbundle 則是 Developit 基於 Rollup 封裝的零配置的輕量級打包工具,其目前已經內建支持 TypeScript 與 Flow,不須要額外的配置;筆者在 js-swissgear/x-fetch 項目的打包中也使用了該工具。

{
  "scripts": {
    "build": "microbundle",
    "dev": "microbundle watch"
  }
}
複製代碼
  • index.js 是 CommonJS 模塊,是 Node.js 內置的模塊類型,使用相似於 require('MyModule') 語法導入
  • index.m.js 是 ECMAScript 模塊,使用相似於 import MyModule from 'my-module' 語法導入
  • index.umd.js 是 UMD 模塊
  • index.d.ts 是 TypeScript 的類型聲明文件

Webpack

做爲著名的打包工具,Webpack 容許咱們指定項目的入口地址,而後自動將用到的資源,經由 Loader 與 Plugin 的轉換,打包到包體文件中。Webpack 相關的項目模板能夠參考:fe-boilerplate/react-webpack, fe-boilerplate/react-webpack-ts, fe-boilerplate/vue-webpack 等。

538c4af0d21e375d6d252d38cbb8a993

Webpack 目前也支持零配置運行

$ npm install webpack webpack-cli webpack-dev-server --save-dev
複製代碼
"scripts": {
  "start": "webpack-dev-server --mode development",
  "build": "webpack --mode production"
},
複製代碼

基礎配置

const config = {
  // 定義入口
  entry: {
    app: path.join(__dirname, 'app')
  },
  // 定義包體文件
  output: {
    // 輸出目錄
    path: path.join(__dirname, 'build'),

    // 輸出文件名
    filename: '[name].js'
    // 使用 hash 做爲文件名
    // filename: "[name].[chunkhash].js",
  },
  // 定義如何處理
  module: {
    rules: [
      {
        test: /\.js$/,
        use: 'babel-loader',
        exclude: /node_modules/
      }
    ]
  },
  // 添加額外插件操做
  plugins: [new webpack.DefinePlugin()]
};
複製代碼

Webpack 一樣支持添加多個配置:

module.exports = [{
  entry: './app.js',
  output: ...,
  ...
}, {
  entry: './app.js',
  output: ...,
  ...
}]
複製代碼

咱們代碼中的 require 與 import 解析規範,則由 resolve 模塊負責,其包含了擴展、別名、模塊等部分:

const config = {
  resolve: {
    alias: {
      /*...*/
    },
    extensions: [
      /*...*/
    ],
    modules: [
      /*...*/
    ]
  }
};
複製代碼

資源加載

const config = {
  module: {
    rules: [
      {
        // **Conditions**
        test: /\.js$/, // Match files
        enforce: 'pre', // "post" too

        // **Restrictions**
        include: path.join(__dirname, 'app'),
        exclude: path => path.match(/node_modules/),

        // **Actions**
        use: 'babel-loader'
      }
    ]
  }
};
複製代碼
// Process foo.png through url-loader and other matches
import 'url-loader!./foo.png';

// Override possible higher level match completely
import '!!url-loader!./bar.png';
複製代碼

babel-loader 或者 awesome-typescript-loader 來處理 JavaScript 或者 TypeScript 文件

/******/ (function(modules) { // webpackBootstrap
...
/* 0 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
 "use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony default export */ __webpack_exports__["default"] = ((text = "Hello world") => {
  const element = document.createElement("div");

  element.innerHTML = text;

  return element;
});

/***/ })
/******/ ]);
複製代碼

use: ["style-loader", "css-loader"] css-loader 會自動地解析 @import 與 url(),而 style-loader 則會將 CSS 注入到 DOM 中,而且實現 HMR 的特性,而對於 SASS、LESS 等 CSS 預處理器,也有專門的 sass-loader 或者 less-loader 來處理;在生產環境下,咱們也經常會將 CSS 抽取到獨立的樣式文件中,此時就可使用 mini-css-extract-plugin (MCEP) 等工具。一樣,咱們可使用 url-loader/file-loader 來處理圖片等資源文件,

代碼分割

代碼分割是提高 Web 性能表現的重要分割,咱們常作的代碼分割也分爲公共代碼提取與按需加載等方式。公共代碼提取便是將第三方渲染模塊或者庫與應用自己的邏輯代碼分割,或者將應用中多個模塊間的公共代碼提取出來,劃分到獨立的 Chunk 中,以方便客戶端進行緩存等操做。

cc11f7e53c579fff28de1b3ed5b9f53a

不一樣於 Webpack 3 中須要依賴 CommonChunksPlugin 進行配置,Webpack 4 引入了 SplitChunksPlugin,併爲咱們提供了開箱即用的代碼優化特性,Webpack 會根據如下狀況自動進行代碼分割操做:

  • 新的塊是在多個模塊間共享,或者來自於 node_modules 目錄;
  • 新的塊在壓縮以前的大小應該超過 30KB;
  • 頁面所需併發加載的塊數量應該小於或者等於 5;
  • 初始頁面加載的塊數量應該小於或者等於 3;

SplitChunksPlugin 的默認配置以下:

splitChunks: {
    chunks: "async",
    minSize: 30000,
    minChunks: 1,
    maxAsyncRequests: 5,
    maxInitialRequests: 3,
    automaticNameDelimiter: '~',
    name: true,
    cacheGroups: {
        vendors: {
            test: /[\\/]node_modules[\\/]/,
            priority: -10
        },
    default: {
            minChunks: 2,
            priority: -20,
            reuseExistingChunk: true
        }
    }
}
複製代碼

值得一提的是,這裏的 chunks 選項有 initial, asyncall 三個配置,上述配置便是分別針對初始 chunks、按需加載的 chunks 與所有的 chunks 進行優化;若是將 vendors 的 chunks 設置爲 initial,那麼它將忽略經過動態導入的模塊包包含的第三方庫代碼。而 priority 則用於指定某個自定義的 Cache Group 捕獲代碼的優先級,其默認值爲 0。在  common-chunk-and-vendor-chunk 例子中,咱們即針對入口進行優化,提取出入口公共的 vendor 模塊與業務模塊:

{
splitChunks: {
			cacheGroups: {
				commons: {
					chunks: "initial",
					minChunks: 2,
					maxInitialRequests: 5, // The default limit is too small to showcase the effect
					minSize: 0 // This is example is too small to create commons chunks
				},
				vendor: {
					test: /node_modules/,
					chunks: "initial",
					name: "vendor",
					priority: 10,
					enforce: true
				}
			}
		}
}
複製代碼

Webpack 的 optimization 還包含了 runtimeChunk 屬性,當該屬性值被設置爲 true 時,即會爲每一個 Entry 添加僅包含運行時信息的 Chunk; 當該屬性值被設置爲 single 時,即爲全部的 Entry 建立公用的包含運行時的 Chunk。咱們也能夠在代碼中使用 import 語句,動態地進行塊劃分,實現代碼的按需加載:

c4e91fafb1a08e7733ac2688222eb65a

// Webpack 3 以後支持顯式指定 Chunk 名
import(/* webpackChunkName: "optional-name" */ './module')
  .then(module => {
    /* ... */
  })
  .catch(error => {
    /* ... */
  });
複製代碼
webpackJsonp([0], {
  KMic: function(a, b, c) {
    ...
  },
  co9Y: function(a, b, c) {
    ...
  },
});
複製代碼

若是是使用 React 進行項目開發,推薦使用 react-loadable 進行組件的按需加載,他可以優雅地處理組件加載、服務端渲染等場景。Webpack 還內建支持基於 ES6 Module 規範的 Tree Shaking 優化,即僅從導入文件中提取出所須要的代碼。

更多關於 Webpack 的使用技巧能夠參閱 Webpack CheatSheet 或者現代 Web 開發基礎與工程實踐/Webpack 章節。

Backpack

Backpack 是面向 Node.js 的極簡構建系統,受 create-react-app, Next.js 以及 Nodemon 的影響,可以以零配置的方式建立 Node.js 項目。Backpack 爲咱們處理了文件監控、熱加載、轉換、打包等工做,默認支持 ECMAScript 最新的 async/await, 對象擴展、類屬性等語法。咱們可使用 npm 安裝依賴:

$ npm i backpack-core --save
複製代碼

而後在 package.json 中配置運行腳本:

{
  "scripts": {
    "dev": "backpack",
    "build": "backpack build"
  }
}
複製代碼

Backend-Boilerplate/node 中能夠查看 Backpack 的典型應用,咱們也能夠覆蓋默認的 Webpack 配置:

// backpack.config.js
module.exports = {
  webpack: (config, options, webpack) => {
    // Perform customizations to config
    // Important: return the modified config
    return config;
  }
};
複製代碼

或者添加 Babel 插件:

{
  "presets": ["backpack-core/babel", "stage-0"]
}
複製代碼
相關文章
相關標籤/搜索