原文連接:https://www.jianshu.com/p/f931f47cbf75javascript
前言: 做爲一個現代javascript 應用程序的靜態模塊打包器,webpack能將各類資源,如js,css, 圖片等做爲模塊來處理,是當下前端工程化的一個很受歡迎的工具,webpack目前最新的版本是4.0,文章將在4.0 的基礎上,從使用者的角度,一步步教你認識並搭建一個簡單的webpack配置項目,固然webpack的配置和使用較爲豐富且複雜,更多的內容須要參考webpack官網css
首先webpack 項目的兩個核心基礎模塊是webpack 和webpack-cli,這是webpack項目構建的前提html
npm install --save-dev webpack webpack-cli
默認狀況下,webpack 運行構建指令默認 以項目文件夾下的 src/index.js 做爲入口文件, 運行 webpack
指令會執行默認的webpack 配置文件。前端
而在通常狀況下,須要構建符合項目要求的配置文件,可在package.json 中同過--config
配置webpack的執行文件(以下)vue
"script": { "build": "webpack --config ./config/webpack.base.js" }
經過指定配置文件後,接下來的工做是根據須要配置執行的配置文件html5
// webpack.base.js module.exports = { }
指定項目的入口文件java
module.exports = { entry: "./****", // 指定入口文件 }
module.exports = { entry: "./****", // 指定入口文件 output: { path: path.resolve(__dirname, ....),// 輸出路徑,通常爲絕對路徑 filename: '****', // 輸出文件名 [hash]能夠用來每次以hash值的區別生成文件 publicPath: 'static', //輸出解析文件的目錄,url 相對於 HTML 頁面 }, chunkFilename: '***' }
注意:node
// publicPath 的解釋 好比 publicPath 設置爲static 以後,html 頁面中引用的url 會自動加上static <!DOCTYPE html> <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>test</title> </head> <body> <script type="text/javascript" src="static/bundle.67c249f5bcf3681ab97e.js"></script></body> </html>
tips: 如何理解chunkFileName:
chunkname 是未被列入entry 中, 卻有須要被打包出來的文件命名配置, 例如,某些公共模塊須要單獨的抽離出來,這些公共模塊就能夠用chunkname 來命名
能夠見下面的代碼分離部分react
{ entry: { app: './src/app.js', search: './src/search.js' }, output: { filename: '[name].js', path: __dirname + '/dist' } } // 寫入到硬盤:./dist/app.js, ./dist/search.js
不斷運行 webpack 的指令,每次都會生成不一樣的不一樣hash 值的js 腳本,所以,咱們須要一個插件,每次構建項目以前,將原先的構建完成的文件夾刪除,首選 clean-webpack-plugin 的插件 配置相關以下webpack
const CleanWebpackPlugin = require('clean-webpack-plugin'); module.exports = { plugins: [ new CleanWebpackPlugin(['dist'], { { root: '', // 刪除文件夾的根路徑 verbose: true, // 是否打開日誌 } }) // 第一個參數爲刪除的文件夾數組 ] }
相關參數配置clean-webpack-plugin
webpack 構建項目時, 經過指定的入口文件,會將全部的js css 等以依賴模塊的形式打包成一個或多個的腳本文件,一般狀況下,腳本文件會附屬於html 文件運行,這時候須要將 打包好的腳本文件,注入到html 中, html-webpack-plugin 插件的目的是, 以一個html 爲模板, 將打包好的腳本注入到模板中, 相關的配置以下
const HtmlWebpackPlugin = require('html-webpack-plugin'); new HtmlWebpackPlugin() // 不帶任何配置時, 默認會以一個內置普通的html 做爲模板html new HtmlWebpackPlugin({ title: 'title', // 給模板中的html 注入標題, 須要在模板的html 中指明配置, <%= htmlWebpackPlugin.options.title %> filename: '', // 指定轉換後的html 文件名 template: './',// 模板文件的路徑 chunk: ['main']// chunk 指定了該模板導入的模塊,在多頁面的配置中,能夠在該屬性中配置多個入口中的一個或者多個腳本文件 })
所謂模式,webpack4.0默認的模式是 'production',能夠經過 mode 來更改模式爲'development'
module.exports = { mode: 'development' // 會將 process.env.NODE_ENV === 'development' mode: 'production' // 會將 process.env.NODE_ENV === 'production' }
生產模式下的要求: 注重模塊的大小
開發模式下的要求: 調試, 熱更新
在生產環境中,默認會進行腳本的壓縮。
在開發環境中,咱們須要快速的調試代碼,所以須要有一個本地的服務器環境,用於訪問 webpack 構建好的靜態文件,webpack-dev-server 是 webpack 官方提供的一個工具,能夠基於當前的 webpack 構建配置快速啓動一個靜態服務。當 mode 爲 development 時,會具有 hot reload 的功能,即當源碼文件變化時,會即時更新當前頁面,以便你看到最新的效果。
根據須要,須要將配置文件抽離成生產配置和開發配置,並留一個共同的配置文件
使用 webpack-merge 來合併對象
npm i --save-dev webpack-dev-server
// package.json { "name": "development", "version": "1.0.0", "description": "", "main": "webpack.config.js", "scripts": { "dev": "webpack-dev-server --open", // webpack-dev-server 啓動 }, "keywords": [], "author": "", "license": "ISC", }
webpack.dev.js const merge = require('webpack-merge') module.exports = merge(base, { devtool: "cheap-module-eval-source-map" devServer: { port: 8088, compress: true, ... } })
講講webpack-dev-server 的配置,webpack-dev-server 的配置比較多,具體能夠參考webpack-dev-server官方文檔
常見的配置:
proxy: { '/api': { target: "http://localhost:3000", // 將 URL 中帶有 /api 的請求代理到本地的 3000 端口的服務上 pathRewrite: { '^/api': '' }, // 把 URL 中 path 部分的 `api` 移除掉 }, }...
devtool 的各類模式會有另外一篇文章進行講解,敬請期待。。。
webpack 中提供一種處理多種文件格式的機制,即是使用 loader。咱們能夠把 loader 理解爲是一個轉換器,負責把某種文件格式的內容轉換成 webpack 能夠支持打包的模塊。
舉個例子,在沒有添加額外插件的狀況下,webpack 會默認把全部依賴打包成 js 文件,若是入口文件依賴一個 .hbs 的模板文件以及一個 .css 的樣式文件,那麼咱們須要 handlebars-loader 來處理 .hbs 文件,須要 css-loader 來處理 .css 文件(這裏其實還須要 style-loader,後續詳解),最終把不一樣格式的文件都解析成 js 代碼,以便打包後在瀏覽器中運行。...
css-loader 負責解析 CSS 代碼,主要是爲了處理 CSS 中的依賴,例如 @import 和 url() 等引用外部文件的聲明
style-loader 會將 css-loader 解析的結果轉變成 JS 代碼,運行時動態插入 style 標籤來讓 CSS 代碼生效。
file-loader 用來處理jpg/png/gif 等文件格式
這兩個loader 用於解析scss 文件
module.exports = { module: { rules: [ { test: /\.(css|scss)$/, use: [ process.env.NODE ? 'style-loader' : MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader' ] } ] } }
爲何要使用babel, 目前低版本的部分瀏覽器,並不支持es6的相關語法,babel 的目的就是爲了講es6 的相關語法,轉換成es5 的語法
npm install -D babel-loader @babel/core @babel/preset-env
webpack 4中須要安裝這三個loader
配置以下:
module.exports = { rules: [ { test: /\.js$/, exclude: /node_modules/, use: { loader: 'babel-loader', options: { presets: ['@babel/preset-env'], } } } ] }
babel的三個loader 能夠解決 箭頭函數這類es6 語法, 可是沒法解決promise, symbol 這類新增的內建對象,
所以須要引入 babel-polyfill
來進行全局的轉換
ps: babel-polyfill 須要放在生產依賴中, 即npm i --save babel-polyfill
而且須要配置 entry
module.exports = { entry: ['babel-polyfill', './src/main.js'], ··· }
使用前
main.f66bf9d8d1fd5b0f62ae.css 88 bytes 0 [emitted] main bundle.f66bf9d8d1fd5b0f62ae.js 66.6 KiB 0 [emitted] main main.f66bf9d8d1fd5b0f62ae.css.map 194 bytes 0 [emitted] main bundle.f66bf9d8d1fd5b0f62ae.js.map 219 KiB 0 [emitted] main
使用後
main.0c090c129f1d9d4804b0.css 88 bytes 0 [emitted] main bundle.0c090c129f1d9d4804b0.js 154 KiB 0 [emitted] main main.0c090c129f1d9d4804b0.css.map 194 bytes 0 [emitted] main bundle.0c090c129f1d9d4804b0.js.map 219 KiB 0 [emitted] main
tips:
能夠很明顯的發現,打包後的bundle會比以前要大大概100k 左右,緣由是, babel-polyfill 會對全局進行改寫,這樣其實壞處是污染了全局的環境,而且增長了打包後的文件大小,這也是要進行安裝在dependency 而不是 devDependency的緣由
所以, webpack4.0 提供了另外的 plugin-transform-runtime
npm install --save-dev @babel/plugin-transform-runtime npm install --save @babel/runtime { test: /\.js$/, exclude: /node_modules/, use: { loader: 'babel-loader', options: { presets: ['@babel/preset-env'], plugins: ['@babel/plugin-transform-runtime'] } } }
在 webpack 的構建流程中,plugin 用於處理更多其餘的一些構建任務。能夠這麼理解,模塊代碼轉換的工做由 loader 來處理,除此以外的其餘任何工做均可以交由 plugin 來完成
uglifyjs-webpack-plugin 是用來對js 代碼進行壓縮體積用的,在webpack4.0中, 默認的配置是進行壓縮,能夠經過 mode 模式的 development 來設置成不進行壓縮,默認模式是production
其餘的默認配置能夠參考:
uglifyjs-webpack-plugin
const UglifyJsPlugin = require('uglifyjs-webpack-plugin') module.exports = { //... optimization: { minimizer: [ new UglifyJsPlugin() ] } }
DefinePlugin 是 webpack 內置的插件,可使用 webpack.DefinePlugin 直接獲取。
這個插件用於建立一些在編譯時能夠配置的全局常量,這些常量的值咱們能夠在 webpack 的配置中去指定,例如:
module.exports = { // ... plugins: [ new webpack.DefinePlugin({ PRODUCTION: JSON.stringify(true), // const PRODUCTION = true VERSION: JSON.stringify('5fa3b9'), // const VERSION = '5fa3b9' BROWSER_SUPPORTS_HTML5: true, // const BROWSER_SUPPORTS_HTML5 = 'true' TWO: '1+1', // const TWO = 1 + 1, CONSTANTS: { APP_VERSION: JSON.stringify('1.1.2') // const CONSTANTS = { APP_VERSION: '1.1.2' } } }), ], }...
有了上面的配置,就能夠在應用代碼文件中,訪問配置好的變量了,如:
console.log("Running App version " + VERSION); if(!BROWSER_SUPPORTS_HTML5) require("html5shiv");
這個插件看名字就知道它有什麼做用,沒錯,就是用來複制文件的。
咱們通常會把開發的全部源碼和資源文件放在 src/ 目錄下,構建的時候產出一個 build/ 目錄,一般會直接拿 build 中的全部文件來發布。有些文件沒通過 webpack 處理,可是咱們但願它們也能出如今 build 目錄下,這時就可使用 CopyWebpackPlugin 來處理了。
咱們來看下如何配置這個插件:...
const CopyWebpackPlugin = require('copy-webpack-plugin') module.exports = { // ... plugins: [ new CopyWebpackPlugin([ { from: 'src/file.txt', to: 'build/file.txt', }, // 顧名思義,from 配置來源,to 配置目標路徑 { from: 'src/*.ico', to: 'build/*.ico' }, // 配置項可使用 glob // 能夠配置不少項複製規則 ]), ], }...
extract-text-webpack-plugin 是在webpack4.0 以前用來把 依賴的css 分離出來成爲單獨的文件,可讓腳本文件變得更小,
webpack 4.0 再也不使用extra-text-webpack-plugin來分離css 轉而使用mini-css-extract-plugin
mini-css-extract-plugin 既須要配置plugin 也須要配置loader
const MiniCssExtractPlugin = require('mini-css-extract-plugin') module.exports = { plugins: [ new MiniCssExtractPlugin({ // Options similar to the same options in webpackOptions.output // both options are optional filename: "[name].[contenthash].css", chunkFilename: "[id].css" }) ], module: { rules: [{ test: /\.(css|scss)$/, include: [path.resolve(__dirname, '../src')], use: [ MiniCssExtractPlugin.loader, 'css-loader' ] }, ] } }
tips: 這個插件通常在生產環境中使用,而且使用時不能使用style-loader==
ps: contenthash 和 hash 的區別 見第八部分
IgnorePlugin 和 ProvidePlugin 同樣,也是一個 webpack 內置的插件,能夠直接使用 webpack.IgnorePlugin 來獲取。
這個插件用於忽略某些特定的模塊,讓 webpack 不把這些指定的模塊打包進去。例如咱們使用 moment.js,直接引用後,裏邊有大量的 i18n 的代碼,致使最後打包出來的文件比較大,而實際場景並不須要這些 i18n 的代碼,這時咱們可使用 IgnorePlugin 來忽略掉這些代碼文件,配置以下:...
module.exports = { // ... plugins: [ new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/) ] }
IgnorePlugin 配置的參數有兩個,第一個是匹配引入模塊路徑的正則表達式,第二個是匹配模塊的對應上下文,即所在目錄名。
爲了實現減少打包後代碼的體積,利用緩存來加速靜態資源訪問,須要將不一樣,且相互不影響的代碼塊分離出來, 在plugin 中介紹過mini-css-extract-plugin
來對css 文件進行分離, 除此以外, 還建議 公共使用的第三方類庫顯式地配置爲公共的部分,由於第三方庫在實際開發中,改變的頻率比較小,能夠避免因公共 chunk 的頻繁變動而致使緩存失效。
module.exports = { entry: { vendor: ["react", "lodash", "angular", ...], // 指定公共使用的第三方類庫 }, optimization: { splitChunks: { cacheGroups: { vendor: { chunks: "initial", test: "vendor", name: "vendor", // 使用 vendor 入口做爲公共部分 enforce: true, }, }, }, }, // ... 其餘配置 } // 或者 module.exports = { optimization: { splitChunks: { cacheGroups: { vendor: { test: /react|angluar|lodash/, // 直接使用 test 來作路徑匹配 chunks: "initial", name: "vendor", enforce: true, }, }, }, }, } // 或者 module.exports = { optimization: { splitChunks: { cacheGroups: { vendor: { chunks: "initial", test: path.resolve(__dirname, "node_modules") // 路徑在 node_modules 目錄下的都做爲公共部分 name: "vendor", // 使用 vendor 入口做爲公共部分 enforce: true, }, }, }, }, }...
ps: 在次基礎上, 須要在配置的output 中設置 chunkFilename 來配置打包後這些第三方庫的名字
module.exports = { output: { path: path.resolve(__dirname, '../dist'), filename: 'bundle.[hash].js', chunkFilename: 'vendor.[chunkhash].js' }, }
ps: hash 和 chunkhash, contenthash 的區別
resolve
配置經常使用模塊的相對路徑
module.exports = { //... resolve: { alias: { Util: path.resolve(__dirname, 'src/util/'), } } }; 實際引用中 imoprt uitl from 'Util/sss' 原始: import util from './src/util/sss'
這個配置能夠定義在進行模塊路徑解析時,webpack 會嘗試幫你補全那些後綴名來進行查找
resolve: { extensions: [".js", ".vue"], },