【webpack】指南

官網指南地址javascript

管理資源&管理輸出

管理資源

webpack 最出色的功能之一就是,除了 JavaScript還能夠經過 loader 引入任何其餘類型的文件webpack.config.js配置以下:css

const path = require('path');
 module.exports = {
    entry: './src/index.js',
    output: {
        filename: 'bundle.js',
        path: path.resolve(__dirname, 'dist')
    },
    module: {
        rules: [
        {
            test: /\.css$/,
            use: [
                'style-loader',
                'css-loader'
            ]
        },
        {
            test: /\.(png|svg|jpg|gif)$/,
            use: [
                'file-loader'
            ]
        },
        {
            test: /\.(woff|woff2|eot|ttf|otf)$/,
            use: [
                'file-loader'
            ]
        },
        {
            test: /\.(csv|tsv)$/,
            use: [
               'csv-loader'
            ]
        },
        {
            test: /\.xml$/,
            use: [
               'xml-loader'
            ]
        }
      ]
    }
};
複製代碼

管理輸出

配置HtmlWebpackPlugin插件自動生成index.html;配置CleanWebpackPlugin插件自動清理dist目錄WebpackManifestPlugin插件能夠提取manifestwebpack.config.js配置以下:html

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');

module.exports = {
    entry: {
        app: './src/index.js',
        print: './src/print.js'
    },
    plugins: [
        new CleanWebpackPlugin(['dist']),
        new HtmlWebpackPlugin({
        title: 'Output Management'
        })
    ],
    output: {
        filename: '[name].bundle.js',
        path: path.resolve(__dirname, 'dist')
    }
};
複製代碼

開發環境&生產環境

  • 開發環境(development)和生產環境(production)的構建目標差別很大。
  • 在開發環境中,咱們須要具備強大的、具備實時從新加載(live reloading)或熱模塊替換(hot module replacement)能力的 source maplocalhost server
  • 而在生產環境中,咱們的目標則轉向於關注更小的 bundle,更輕量的 source map,以及更優化的資源,以改善加載時間。
  • 因爲要遵循邏輯分離,咱們一般建議爲每一個環境編寫彼此獨立的 webpack 配置。
  • 可是,咱們會遵循不重複原則,保留一個「通用」配置。咱們將使用一個名爲 webpack-merge 的工具合併配置
npm install --save-dev webpack-merge
複製代碼

開發模式&模塊熱替換

生產環境

  1. 代碼壓縮:UglifyJSPlugin或其餘工具
  2. source map用仍是不用?
  3. 許多 library 將經過與 process.env.NODE_ENV 環境變量關聯,以決定 library 中應該引用哪些內容
new webpack.DefinePlugin({
   'process.env.NODE_ENV': JSON.stringify('production')
})
複製代碼
  1. ExtractTextPlugin:將 CSS 分離成單獨的文件
  2. CLI 替代選項:原來用的一些插件如今能夠替換成一些優化配置項:例如,--optimize-minimize 標記將在後臺引用 UglifyJSPlugin。和以上描述的 DefinePlugin 實例相同,--define process.env.NODE_ENV="'production'" 也會作一樣的事情。而且,webpack -p 將自動地調用上述這些標記,從而調用須要引入的插件。

構建性能

  1. 保持版本最新webpack/node/npm
  2. 儘可能少使用不一樣的工具loader/plugins;必要的話也用在儘可能最少數的必要模塊中;能夠將很是消耗資源的 loaders 轉存到 worker pool
//使用 include 字段僅將 loader 模塊應用在實際須要用其轉換的位置中:
{
  test: /\.js$/,
  include: path.resolve(__dirname, "src"),
  loader: "babel-loader"
}
複製代碼
  1. Dlls:使用 DllPlugin更改不頻繁的代碼進行單獨編譯。這將改善引用程序的編譯速度,即便它增長了構建過程的複雜性。
  2. Smaller = Faster:減小編譯的總體大小,以提升構建性能。儘可能保持 chunks 小巧。
  3. 使用 cache-loader 啓用持久化緩存。使用 package.json 中的 postinstall 清除緩存目錄。thread-loader 能夠將很是消耗資源的 loaders 轉存到 worker pool
  4. 提升解析速度:
    • 儘可能減小 resolve.modules, resolve.extensions, resolve.mainFiles, resolve.descriptionFiles 中類目的數量,由於他們會增長文件系統調用的次數。
    • 若是你不使用 symlinks ,能夠設置 resolve.symlinks: false
    • 若是你使用自定義解析 plugins ,而且沒有指定 context 信息,能夠設置 resolve.cacheWithContext: false
  5. 開發環境中
    • 硬避免在生產環境下才會用到的工具:UglifyJsPlugin、ExtractTextPlugin、[hash]/[chunkhash]、AggressiveSplittingPlugin、AggressiveMergingPlugin、ModuleConcatenationPlugin
    • 不一樣的 devtool 的設置,會致使不一樣的性能差別
    • 在內存中進行代碼的編譯和資源的提供,但並不寫入磁盤來提升性能:webpack-dev-server
    • 儘可能減小入口 chunk 的體積,以提升性能
  6. 其餘

tree shaking

tree shaking 是一個術語,一般用於描述移除 JavaScript 上下文中的未引用代碼(dead-code)。新的 webpack 4 正式版本,擴展了這個檢測能力,經過 package.jsonsideEffects 屬性做爲標記,向 compiler 提供提示,代表項目中的哪些文件是 pure(純的 ES2015 模塊)",由此能夠安全地刪除文件中未使用的部分。vue

  1. sideEffects:將文件標記爲無反作用 如同上面提到的,若是全部代碼都不包含反作用,咱們就能夠簡單地將該屬性標記爲 false,來告知 webpack,它能夠安全地刪除未用到的 export 導出。
{
  "name": "your-project",
  "sideEffects": false
}
複製代碼

若是你的代碼確實有一些反作用,那麼能夠改成提供一個數組:java

{
  "name": "your-project",
  "sideEffects": [
    "./src/some-side-effectful-file.js"
  ]
}
複製代碼
  1. 壓縮輸出:經過如上方式,咱們能夠,找出那些須要刪除的「未使用代碼」,然而,咱們不僅是要找出,還須要在 bundle 中刪除它們。爲此,咱們將使用 -p(production) 這個 webpack 編譯標記,來啓用 uglifyjs 壓縮插件。
    • 注意,--optimize-minimize 標記也會在 webpack 內部調用 UglifyJsPlugin。)
    • webpack 4 開始,也能夠經過 mode 配置選項輕鬆切換到壓縮輸出,只需設置爲 production

懶加載(動態導入/按需加載)

vue單頁應用中,當項目不斷完善豐富時,即便使用webpack打包,文件依然是很是大的,影響頁面的加載。若是咱們能把不一樣路由對應的組件分割成不一樣的代碼塊,當路由被訪問時才加載對應的組件(也就是按需加載),這樣就更加高效了。——引自vue-router官方文檔node

非動態加載時的打包狀況以下圖: jquery

以下案例中 hello組件的加載方式 改成路由懶加載[import()語法],在進行打包

// import Hello from '@/components/Hello'
export default new Router({
  routes: [
    {
      path: '/',
      name: 'Hello',
      // component: Hello,
      component: () => import('@/components/Hello')
    }
  ]
})
複製代碼

  • 很明顯的看到,打包後有4個js文件,仔細的同窗還發現,app.js文件的大小加上新多出文件的大小,約等於沒有分割打包的app的大小。
  • 這樣等於異步加載的組件,是單獨打包成了一個js,在頁面首次加載的時候不須要加載他,等到請求相應的頁面的時候在去服務器請求它,減少了頁面首屏加載的時間
  • webpack.prod.conf.js 中配置 output.chunkFilename 規定了打包異步文件的格式
//webpack.prod.conf.js:生產打包js
//utils.assetsPath是utils.js中封裝的一個路徑相關的方法
output: {
    path: config.build.assetsRoot,
    filename: utils.assetsPath('js/[name].[chunkhash].js'),
    /** 本人在本身的工程中刪除或修改chunkFilename的配置, 最後仍是都按這個規則生產js文件了, Why? */
    chunkFilename: utils.assetsPath('js/[id].[chunkhash].js') 
},
複製代碼
  • 不須要刻意懶加載。下面的案例中,代碼確實會在腳本運行的時候產生一個分離的代碼塊 lodash.bundle.js,在技術概念上「懶加載」它。問題是加載這個包並不須要用戶的交互 -- 意思是每次加載頁面的時候都會請求它。這樣作並無對咱們有不少幫助,還會對性能產生負面影響。
//src/index.js
- import _ from 'lodash';
-
- function component() {
+ function getComponent() {
-   var element = document.createElement('div');
-
-   // Lodash, now imported by this script
-   element.innerHTML = _.join(['Hello', 'webpack'], ' ');
+   return import(/* webpackChunkName: "lodash" */ 'lodash').then(_ => {
+     var element = document.createElement('div');
+
+     element.innerHTML = _.join(['Hello', 'webpack'], ' ');
+
+     return element;
+
+   }).catch(error => 'An error occurred while loading the component');
  }

- document.body.appendChild(component());
+ getComponent().then(component => {
+   document.body.appendChild(component);
+ })
複製代碼
  • 當頁面有個按鈕點擊事件時,須要加載某個組件,這種用戶交互的情景下,咱們可使用懶加載;固然,路由也是交互的一種。

緩存

客戶端(一般是瀏覽器)獲取資源是比較耗費時間的。能夠經過命中緩存,以下降網絡流量,使網站加載速度更快;然而,緩存的存在可能會使你獲取新代碼時比較棘手(文件名不變的話)。如何經過必要的配置,以確保 webpack 編譯生成的文件可以被客戶端緩存,而在文件內容變化後,可以請求到新的文件webpack

  • 經過使用 output.filename 進行文件名替換
  • 若是咱們不作修改,而後再次運行構建,咱們覺得文件名會保持不變。然而,若是咱們真的運行,可能會發現狀況並不是如此(譯註:若是不作修改,文件名可能會變,也可能不會。
  • 這也是由於 webpack 在入口 chunk 中,包含了某些樣板(boilerplate),特別是 runtimemanifest譯註:樣板(boilerplate)指 webpack 運行時的引導代碼
  • 即:若是 webpack 生成的 hash 發生改變,manifest 文件也會發生改變。所以,vendor bundle 的內容也會發生改變,而且失效。因此,咱們須要將 manifest 文件提取出來。
  • CommonsChunkPluginmanifest提取出來
  • webpack 裏每一個模塊都有一個 module idmodule id 是該模塊在模塊依賴關係圖裏按順序分配的序號,若是這個 module id 發生了變化,那麼他的 chunkhash 也會發生變化。
  • 這樣會致使:若是你引入一個新的模塊,會致使module id 總體發生改變,可能會致使全部文件的chunkhash發生變化,這顯然不是咱們想要的
  • 這裏須要用 HashedModuleIdsPlugin,根據模塊的相對路徑生成一個四位數的hash做爲模塊id,這樣就算引入了新的模塊,也不會影響 module id 的值,只要模塊的路徑不改變的話。
new webpack.HashedModuleIdsPlugin()
複製代碼

建立library

除了打包應用程序代碼,webpack 還能夠用於打包 JavaScript library;即咱們平時npm install下來的那種依賴包git

個人demo:element-ui表單的二次封裝github

最後,package.json中配置"main": "dist/ginna-form.js";咱們從node_modules引入時就靠這個屬性來找到對應的文件的哦。


shimming

webpack 編譯器(compiler)可以識別遵循 ES2015 模塊語法、CommonJSAMD 規範編寫的模塊。然而,一些第三方的庫(library)可能會引用一些全局依賴(例如 jQuery 中的 $)。這些庫也可能建立一些須要被導出的全局變量這些「不符合規範的模塊」就是 shimming 發揮做用的地方

shim:一種庫(library)的抽象,這種庫能將一個新的 API 引入到一箇舊的環境中,並且僅靠舊的環境中已有的手段實現。polyfill 就是一個用在瀏覽器 API 上的 shim

1. 讓jQuery做爲全局變量,能夠被別的組件引用

2. 將模塊中的this置爲window

當模塊運行在 CommonJS 環境下this會變成一個問題,也就是說此時的 this 指向的是 module.exports。在這個例子中,你能夠經過使用 imports-loader 覆寫 this

3. 某個庫(library)建立出一個全局變量,它指望用戶使用這個變量

  • 你可能歷來沒有在本身的源碼中作過這些事情,可是你也許遇到過一個老舊的庫(library),和下面所展現的代碼相似。
  • 在下圖用例中,咱們可使用exports-loader,將一個全局變量做爲一個普通的模塊來導出。例如,爲了將 file 導出爲 file 以及將 helpers.parse 導出爲 parse

4. babel-polyfill 按需加載

  • Babel是一個普遍使用的轉碼器,能夠將ES6代碼轉爲ES5代碼,從而能夠在現有環境執行,因此咱們能夠用ES6編寫,而不用考慮環境支持的問題。
  • Babel默認只轉換新的JavaScript語法(syntax),如箭頭函數等,而不轉換新的API,好比Iterator、Generator、Set、Maps、Proxy、Reflect、Symbol、Promise等全局對象,以及一些定義在全局對象上的方法(好比Object.assign)都不會轉碼;所以咱們須要polyfill

5. 深度優化包babel-preset-env

  • babel-preset-env 是一個新的preset,能夠根據配置的目標運行環境(environment)自動啓用須要的 babel 插件
  • 以前咱們寫 javascript 代碼時,須要使用 Npreset,好比:babel-preset-es201五、babel-preset-es2016es2015 能夠把 ES6 代碼編譯爲 ES5es2016能夠把 ES2016 代碼編譯爲 ES6babel-preset-latest 能夠編譯 stage 4 進度的 ECMAScript 代碼。
  • 問題是咱們幾乎每一個項目中都使用了很是多的preset,包括沒必要要的。
  • babel-preset-env 的工做方式相似 babel-preset-latest,惟一不一樣的就是它會根據配置的 env 只編譯那些還不支持的特性。
  • 使用這個插件,你講不再須要使用 es20xx presets 了。

6. 其餘工具

  • 還有一些其餘的工具可以幫助咱們處理這些老舊的模塊。
  • script-loader 會在全局上下文中對代碼進行取值,相似於經過一個 script 標籤引入腳本。在這種模式下,每個標準的庫(library)都應該能正常運行
  • 這些老舊的模塊若是沒有 AMD/CommonJS 規範版本,但你也想將他們加入 dist 文件,你可使用 noParse 來標識出這個模塊
noParse:這是module中的一個屬性,
做用:不去解析屬性值表明的庫的依賴

舉例:
咱們通常引用jquery,能夠以下引用:
import jq from 'jquery'

對於上面的解析規則:
當解析jq的時候,會去解析jq這個庫是否有依賴其餘的包

咱們對相似jq這類依賴庫,通常會認爲不會引用其餘的包(特殊除外,自行判斷)。
因此,對於這類不引用其餘的包的庫,咱們在打包的時候就沒有必要去解析,
這樣可以增長打包速率。
因此,能夠在webpack的配置中增長noParse屬性
(如下代碼只須要看module的noParse屬性)

module.exports = {
	mode:'development',
	entry:'./src/index.js',
	output:{
		filename:'bundle.js',
		path:path.resolve(__dirname,'dist')
	},
	module:{
		noParse:/jquery/,//不去解析jquery中的依賴庫
		rules:[ ]
	}
}
複製代碼

漸進式網絡應用程序PWA

漸進式網絡應用程序(Progressive Web Application - PWA),是一種能夠提供相似於原生應用程序(native app)體驗的網絡應用程序(web app)。PWA 能夠用來作不少事。其中最重要的是,在離線(offline)時應用程序可以繼續運行功能。這是經過使用名爲 Service Workers 的網絡技術來實現的。

//print.js
export default function printMe() {
  console.log('I get called from print.js!');
}

//index.js
import printMe from './print.js';

if ('serviceWorker' in navigator) {
   window.addEventListener('load', () => {
     navigator.serviceWorker.register('./service-worker.js').then(registration => {
       console.log('SW registered: ', registration);
     }).catch(registrationError => {
       console.log('SW registration failed: ', registrationError);
     });
   });
}

function component() {
	var element = document.createElement('div');
	element.innerHTML = _.join(['Hello', 'webpack'], ' ');
	var btn = document.createElement('button');
	btn.innerHTML = 'Click me and check the console!';
    btn.onclick = printMe;
    element.appendChild(btn);
	return element;
}
document.body.appendChild(component());

//webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const WorkboxPlugin = require('workbox-webpack-plugin');

module.exports = {
    entry: {
      app: './src/index.js',
      print: './src/print.js'
    },
    //3個插件都須要npm install --save-dev
    plugins: [
        new CleanWebpackPlugin(['dist']),
        new HtmlWebpackPlugin({
            title: 'Output Management'
            title: 'Progressive Web Application'
        }),
        //添加 Workbox
        new WorkboxPlugin.GenerateSW({
            // 這些選項幫助 ServiceWorkers 快速啓用
            // 不容許遺留任何「舊的」 ServiceWorkers
            clientsClaim: true,
            skipWaiting: true
        })
    ],
    output: {
        filename: '[name].bundle.js',
        path: path.resolve(__dirname, 'dist')
    }
};

//package.json配置腳本
"scripts": {
    "build": "webpack",
    //使用一個簡易服務器,搭建出咱們所需的離線體驗
    //npm install http-server --save-dev
    "start": "http-server dist"
  },
複製代碼
  • 有了 Workbox,咱們再看下執行 npm run build 時會發生什麼
clean-webpack-plugin: /mnt/c/Source/webpack-follow-along/dist has been removed.
Hash: 6588e31715d9be04be25
Version: webpack 3.10.0
Time: 782ms
                                                Asset       Size  Chunks                    Chunk Names
                                        app.bundle.js     545 kB    0, 1  [emitted]  [big]  app
                                      print.bundle.js    2.74 kB       1  [emitted]         print
                                           index.html  254 bytes          [emitted]
precache-manifest.b5ca1c555e832d6fbf9462efd29d27eb.js  268 bytes          [emitted]
                                    service-worker.js       1 kB          [emitted]
   [0] ./src/print.js 87 bytes {0} {1} [built]
   [1] ./src/index.js 477 bytes {0} [built]
   [3] (webpack)/buildin/global.js 509 bytes {0} [built]
   [4] (webpack)/buildin/module.js 517 bytes {0} [built]
    + 1 hidden module
Child html-webpack-plugin for "index.html":
     1 asset
       [2] (webpack)/buildin/global.js 509 bytes {0} [built]
       [3] (webpack)/buildin/module.js 517 bytes {0} [built]
        + 2 hidden modules
複製代碼
  • 如今你能夠看到,生成了 2 個額外的文件:service-worker.js(或sw.js) 和體積很大的 precache-manifest.b5ca1c555e832d6fbf9462efd29d27eb.jssw.jsService Worker 文件,precache-manifest.b5ca1c555e832d6fbf9462efd29d27eb.jssw.js 引用的文件,因此它也能夠運行
  • 而後用 npm start 啓動服務。訪問 http://localhost:8080/index.html 並查看 console 控制檯。在那裏你應該看到:
SW registered
複製代碼
  • 如今來進行測試。中止服務器並刷新頁面。若是瀏覽器可以支持 Service Worker,你應該能夠看到你的應用程序還在正常運行。然而,服務器已經中止了服務,此刻是 Service Worker 在提供服務。

TypeScript

準備工做:ts-loader插件、tsconfig.json配置文件(和package.json同級)、ts文件中如何使用第三方庫(ts聲明文件*.d.ts

  1. webpack.config.js配置
  2. tsconfig.json案例:
  3. 使用第三方庫 當從 npm 安裝第三方庫時,必定要牢記同時安裝這個庫的類型聲明文件。你能夠從 TypeSearch 中找到並安裝這些第三方庫的類型聲明文件。舉個例子,若是想安裝 lodash 這個庫的類型聲明文件,咱們能夠運行下面的命令:npm install --save-dev @types/lodash
  4. TypeScript學習
相關文章
相關標籤/搜索