webpack5 預計會在 2020 年年初發布,以前從 alpha 版本就有關注,本次重點更新在長期緩存,tree shakking 和 es6 打包這塊。具體變動能夠參考https://github.com/webpack/ch...。css
webpack 是現代前端開發中最火的模塊打包工具,只須要經過簡單的配置,即可以完成模塊的加載和打包。那它是怎麼作到經過對一些插件的配置,即可以輕鬆實現對代碼的構建呢?html
本篇文章不會去探討 webpack5 中所要更新的內容,我相信大多數前端同窗對於 webpack 只是會簡單的配置,並且如今像 vue-cli、umi 等對於 webpack 都有很好的封裝,但其實這樣對於咱們本身是不太好的。尤爲是想針對業務場景去作一些個性化的定製時。只有對 webpack 中的細節足夠了解,咱們才能遊刃有餘,本文將從 webpack 現有的大版本 webpack4,帶你一步步打造極致的前端開發環境。前端
global
(全局):經過 webpack index.js
運行local
(項目維度安裝):經過 npx webpack index.js
運行避免全局安裝 webpack(針對多個項目採用不一樣的 webpack 版本進行打包的場景),可採用npx
vue
// webpack.config.js const config = { entry: { main: "./src/index.js" } };
// webpack.config.js const config = { entry: { main: "./src/index.js", sub: "./src/sub.js" } };
// webpack.config.js const path = require('path'); ... const config = { output: { filename: 'bundle.js', path: path.resolve(__dirname, 'dist') } }; module.exports = config;
若是配置建立了多個單獨的 "chunk"(例如,使用多個入口起點或使用像 CommonsChunkPlugin 這樣的插件),則應該使用佔位符(substitutions)來確保每一個文件具備惟一的名稱。
// webpack.config.js const path = require('path'); { entry: { main: './src/index.js', sub: './src/sub.js' }, output: { filename: '[name].js', path: path.resolve(__dirname, 'dist') } } // 寫入到硬盤:./dist/main.js, ./dist/sub.js
使用 cdn
// webpack.config.js const path = require('path'); { entry: { main: './src/index.js', sub: './src/sub.js' }, output: { publicPath: 'http://cdn.example.com' filename: '[name].js', path: path.resolve(__dirname, 'dist') } } // 寫入到http://cdn.example.com/main.js, http://cdn.example.com/sub.js
webpack 可使用 loader 來預處理文件。這容許你打包除 JavaScript 以外的任何靜態資源。
rules: [ { test: /\.(jpg|png|gif)$/, use: { loader: "file-loader", options: { name: "[name]_[hash].[ext]", outputPath: "images/" } } } ];
rules: [ { test: /\.(jpg|png|gif)$/, use: { loader: "url-loader", options: { name: "[name]_[hash].[ext]", outputPath: "images/", limit: 204800 } } } ];
module: { rules: [ { test: /\.css$/, use: ["style-loader", "css-loader"] } ]; }
yarn add sass-loader node-sass webpack --dev
node
// webpack.config.js module.exports = { ... module: { rules: [{ test: /\.scss$/, use: [{ loader: "style-loader" // 將 JS 字符串生成爲 style 節點 }, { loader: "css-loader" // 將 CSS 轉化成 CommonJS 模塊 }, { loader: "sass-loader" // 將 Sass 編譯成 CSS }] }] } };
https://blog.csdn.net/u014628388/article/details/82593185
// webpack.config.js { test: /\.scss$/, use: [ 'style-loader', 'css-loader', 'sass-loader', 'postcss-loader' ], } //postcss.config.js module.exports = { plugins: [ require('autoprefixer')({ browsers: ['last 2 versions'] }), ], };
plugin 能夠在 webpack 運行到某個時刻的時候,幫你作一些事情
const HtmlWebpackPlugin = require('html-webpack-plugin'); module.exports = { ... plugins: [ new HtmlWebpackPlugin({ template: 'src/index.html' }), ], };
https://blog.csdn.net/qq_23521659/article/details/88353708
const { CleanWebpackPlugin } = require('clean-webpack-plugin'); module.exports = { ... plugins: [ new CleanWebpackPlugin() ], output: { filename: 'bundle.js', path: path.resolve(__dirname, 'dist') }
https://juejin.im/post/5af15e895188256715479a9a
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 } } }
將 CSS 提取爲獨立的文件的插件,對每一個包含 css 的 js 文件都會建立一個 CSS 文件,支持按需加載 css 和 sourceMap
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].css", chunkFilename: "[id].css" }) ], module: { rules: [ { test: /\.scss$/, use: [ { loader: MiniCssExtractPlugin.loader }, { loader: "css-loader", options: { importLoaders: 2 // 用於指定在 css-loader 前應用的 loader 的數量 // modules: true // 查詢參數 modules 會啓用 CSS 模塊規範 } }, "sass-loader", "postcss-loader" ] }, { test: /\.css$/, use: [ { loader: MiniCssExtractPlugin.loader }, "css-loader", "postcss-loader" ] } ] } };
webpack5 可能會內置 CSS 壓縮器,webpack4 須要本身使用壓縮器,可使用 optimize-css-assets-webpack-plugin 插件。 設置 optimization.minimizer 覆蓋 webpack 默認提供的,確保也指定一個 JS 壓縮器
const UglifyJsPlugin = require("uglifyjs-webpack-plugin"); const MiniCssExtractPlugin = require("mini-css-extract-plugin"); const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin"); module.exports = { optimization: { minimizer: [ new UglifyJsPlugin({ cache: true, parallel: true, sourcMap: true }), new OptimizeCSSAssetsPlugin({}) ] }, plugins: [ new MiniCssExtractPlugin({ filename: "[name].css", chunkFilename: "[id].css" }) ], module: { rules: [ { test: /\.css$/, use: [MiniCssExtractPlugin.loader, "css-loader"] } ] } };
source map 就是對打包生成的代碼與源代碼的一種映射,主要是爲了方便定位問題和排查問題。devtool 關鍵有 eval、cheap、module、inline 和 source-map 這幾塊,具體可參考文檔:
https://www.webpackjs.com/configuration/devtool/
'cheap-module-eval-source-map'
'cheap-module-source-map'
webpack-dev-server 提供了一個簡單的 web 服務器,而且可以實時從新加載(live reloading)。具體可參考
https://www.webpackjs.com/guides/development/#%E4%BD%BF%E7%94%A8-webpack-dev-server
若是你有單獨的後端開發服務器 API,而且但願在同域名下發送 API 請求 ,那麼代理某些 URL 會頗有用。dev-server 使用了很是強大的http-proxy-middleware
包。經常使用於接口請求轉發。具體參考https://www.webpackjs.com/configuration/dev-server/#devserver-proxy
devServer: { contentBase: "./dist", open: true, hot: true, hotOnly: true, proxy: { "/api": { target: "https://other-server.example.com", pathRewrite: {"^/api" : ""}, secure: false, bypass: function(req, res, proxyOptions) { if (req.headers.accept.indexOf("html") !== -1) { console.log("Skipping proxy for browser request."); return "/index.html"; } } } } },
當使用 HTML5 History API 時,任意的 404 響應均可能須要被替代爲 index.html
經過傳入如下啓用:
historyApiFallback: true;
經過傳入一個對象,好比使用 rewrites 這個選項,此行爲可進一步地控制:react
historyApiFallback: { rewrites: [ { from: /^\/$/, to: "/views/landing.html" }, { from: /^\/subpage/, to: "/views/subpage.html" }, { from: /./, to: "/views/404.html" } ]; }
webpack-dev-middleware 是一個容器(wrapper),它能夠把 webpack 處理後的文件傳遞給一個服務器(server)。 webpack-dev-server 在內部使用了它,同時,它也能夠做爲一個單獨的包來使用,以便進行更多自定義設置來實現更多的需求
// server.js // 使用webpack-dev-middleware // https://www.webpackjs.com/guides/development/#%E4%BD%BF%E7%94%A8-webpack-dev-middleware const express = require("express"); const webpack = require("webpack"); const webpackDevMiddleware = require("webpack-dev-middleware"); const config = require("./webpack.config.js"); const complier = webpack(config); const app = express(); app.use( webpackDevMiddleware(complier, { publicPath: config.output.publicPath }) ); app.listen(3000, () => { console.log("server is running"); });
模塊熱替換(Hot Module Replacement 或 HMR)是 webpack 提供的最有用的功能之一。它容許在運行時更新各類模塊,而無需進行徹底刷新。
// webpack.config.js ... const webpack = require('webpack'); ... devServer: { contentBase: './dist', open: true, hot: true, hotOnly: true }, plugins: [ ... new webpack.HotModuleReplacementPlugin() ],
若是已經經過 HotModuleReplacementPlugin 啓用了模塊熱替換(Hot Module Replacement),則它的接口將被暴露在 module.hot 屬性下面。一般,用戶先要檢查這個接口是否可訪問,而後再開始使用它。webpack
// index.js if (module.hot) { module.hot.accept("./library.js", function() { // 使用更新過的 library 模塊執行某些操做... }); }
藉助一些官方推薦的可視化分析工具,可對打包後的模塊進行分析以及優化
webpack-chart
: webpack 數據交互餅圖webpack-visualizer
: 可視化並分析你的 bundle,檢查哪些模塊佔用空間,哪些多是重複使用的webpack-bundle-analyzer
: 一款分析 bundle 內容的插件及 CLI 工具,以便捷的、交互式、可縮放的樹狀圖形式展示給用戶prefetch:會等待覈心代碼加載完成後,頁面帶寬空閒後再去加載 prefectch 對應的文件;preload:和主文件一塊兒去加載
module: { rules: [ { test: /\.js$/, exclude: /node_modules/, use: { loader: "babel-loader" } } ]; }
新建.babelrc 文件css3
{ "presets": ["@babel/preset-env", "@babel/preset-react"], "plugins": ["@babel/plugin-transform-runtime"] }
在 src 下的 index.js 中全局引入@babel/polyfill 並寫入 es6 語法,可是這樣有一個缺點:
全局引入@babel/polyfill 的這種方式可能會導入代碼中不須要的 polyfill,從而使打包體積更大,修改.babelrc 配置git
`yarn add core-js@2 @babel/runtime-corejs2 --dev` { "presets": [ [ "@babel/preset-env", { "useBuiltIns": "usage" } ], "@babel/preset-react" ], "plugins": ["@babel/plugin-transform-runtime"] }
這就配置好了按需引入。配置了按需引入 polyfill 後,用到 es6 以上的函數,babel 會自動導入相關的 polyfill,這樣能大大減小打包編譯後的體積。es6
參考
https://www.jianshu.com/p/73ba084795ce
tree shaking 可清除代碼中無用的 js 代碼,只支持 import 方式引入,不支持 commonjs 的方式引入
mode 是 production 的無需配置,下面的配置是針對 development 的
// webpack.config.js optimization: { usedExports: true } // package.json "sideEffects": false,
代碼分割,和 webpack 無關
// index.js import _ from 'lodash'; console.log(_.join(['a','b','c'], '****')) // 在webpack.base.js裏作相關配置 optimization: { splitChunks: { chunks: 'all' } },
@babel/plugin-syntax-dynamic-import
包)// index.js function getComponent() { return import("lodash").then(({ default: _ }) => { const element = document.createElement("div"); element.innerHTML = _.join(["Jack", "Cool"], "-"); return element; }); } getComponent().then(el => { document.body.appendChild(el); });
經過使用 output.filename 進行文件名替換,能夠確保瀏覽器獲取到修改後的文件。[hash] 替換能夠用於在文件名中包含一個構建相關(build-specific)的 hash,可是更好的方式是使用 [contenthash] 替換,當文件內容發生變化時,[contenthash]也會發生變化
output: { filename: "[name].[contenthash].js", chunkFilename: '[name].[contenthash].chunk.js' }
webpack 編譯器(compiler)可以識別遵循 ES2015 模塊語法、CommonJS 或 AMD 規範編寫的模塊。然而,一些第三方的庫(library)可能會引用一些全局依賴(例如 jQuery 中的 $)。這些庫也可能建立一些須要被導出的全局變量。這些「不符合規範的模塊」就是 shimming 發揮做用的地方
const path = require('path'); + const webpack = require('webpack'); module.exports = { entry: './src/index.js', output: { filename: 'bundle.js', path: path.resolve(__dirname, 'dist') - } + }, + plugins: [ + new webpack.ProvidePlugin({ + _: 'lodash' + }) + ] };
const path = require('path'); const webpack = require('webpack'); module.exports = { entry: './src/index.js', output: { filename: 'bundle.js', path: path.resolve(__dirname, 'dist') }, + module: { + rules: [ + { + test: require.resolve('index.js'), + use: 'imports-loader?this=>window' + } + ] + }, plugins: [ new webpack.ProvidePlugin({ join: ['lodash', 'join'] }) ] };
webpack 命令行環境選項 --env 容許您傳入任意數量的環境變量。您的環境變量將可訪問 webpack.config.js。例如,--env.production 或--env.NODE_ENV=local
webpack --env.NODE_ENV=local --env.production --progress
使用環境變量必須對 webpack 配置進行一項更改。一般,module.exports 指向配置對象。要使用該 env 變量,必須轉換 module.exports 爲函數:
// webpack.config.js const path = require("path"); module.exports = env => { // Use env.<YOUR VARIABLE> here: console.log("NODE_ENV: ", env.NODE_ENV); // 'local' console.log("Production: ", env.production); // true return { entry: "./src/index.js", output: { filename: "bundle.js", path: path.resolve(__dirname, "dist") } }; };
除了打包應用程序代碼,webpack 還能夠用於打包 JavaScript library
用戶應該可以經過如下方式訪問 library:
咱們打包的 library 中可能會用到一些第三方庫,諸如 lodash。如今,若是執行 webpack,你會發現建立了一個很是巨大的文件。若是你查看這個文件,會看到 lodash 也被打包到代碼中。在這種場景中,咱們更傾向於把 lodash 看成 peerDependency。也就是說,用戶應該已經將 lodash 安裝好。所以,你能夠放棄對外部 library 的控制,而是將控制權讓給使用 library 的用戶。這可使用 externals 配置來完成:
// webpack.config.js var path = require('path'); module.exports = { entry: './src/index.js', output: { path: path.resolve(__dirname, 'dist'), filename: 'webpack-numbers.js' - } + }, + externals: { + lodash: { + commonjs: 'lodash', + commonjs2: 'lodash', + amd: 'lodash', + root: '_' + } + } };
對於用途普遍的 library,咱們但願它可以兼容不一樣的環境,例如 CommonJS,AMD,Node.js 或者做爲一個全局變量。爲了讓你的 library 可以在各類用戶環境(consumption)中可用,須要在 output 中添加 library 屬性:
// webpack.config.js var path = require('path'); module.exports = { entry: './src/index.js', output: { path: path.resolve(__dirname, 'dist'), - filename: 'library.js' + filename: 'library.js', + library: 'library' }, externals: { lodash: { commonjs: 'lodash', commonjs2: 'lodash', amd: 'lodash', root: '_' } } };
當你在 import 引入模塊時,這能夠將你的 library bundle 暴露爲名爲 webpackNumbers 的全局變量。爲了讓 library 和其餘環境兼容,還須要在配置文件中添加 libraryTarget 屬性。這是能夠控制 library 如何以不一樣方式暴露的選項。
var path = require('path'); module.exports = { entry: './src/index.js', output: { path: path.resolve(__dirname, 'dist'), filename: 'library.js', + library: 'library', + libraryTarget: 'umd' }, externals: { lodash: { commonjs: 'lodash', commonjs2: 'lodash', amd: 'lodash', root: '_' } } };
咱們還須要經過設置 package.json 中的 main 字段,添加生成 bundle 的文件路徑。
// package.json { ... "main": "dist/library.js", ... }
漸進式網絡應用程序(Progressive Web Application - PWA),是一種能夠提供相似於原生應用程序(native app)體驗的網絡應用程序(web app)。PWA 能夠用來作不少事。其中最重要的是,在離線(offline)時應用程序可以繼續運行功能。這是經過使用名爲 Service Workers 的網絡技術來實現的
添加 workbox-webpack-plugin 插件,並調整 webpack.config.js 文件:
npm install workbox-webpack-plugin --save-dev
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' }, plugins: [ new CleanWebpackPlugin(['dist']), new HtmlWebpackPlugin({ - title: 'Output Management' + title: 'Progressive Web Application' - }) + }), + new WorkboxPlugin.GenerateSW({ + // 這些選項幫助 ServiceWorkers 快速啓用 + // 不容許遺留任何「舊的」 ServiceWorkers + clientsClaim: true, + skipWaiting: true + }) ], output: { filename: '[name].bundle.js', path: path.resolve(__dirname, 'dist') } };
註冊 Service Worker
import _ from 'lodash'; import printMe from './print.js'; + if ('serviceWorker' in navigator) { + window.addEventListener('load', () => { + navigator.serviceWorker.register('/sw.js').then(registration => { + console.log('SW registered: ', registration); + }).catch(registrationError => { + console.log('SW registration failed: ', registrationError); + }); + }); + }
如今來進行測試。中止服務器並刷新頁面。若是瀏覽器可以支持 Service Worker,你應該能夠看到你的應用程序還在正常運行。然而,服務器已經中止了服務,此刻是 Service Worker 在提供服務。
可參考https://www.webpackjs.com/guides/typescript/
或https://webpack.js.org/guides/typescript/
npm install --save-dev typescript ts-loader
{ "compilerOptions": { "outDir": "./dist/", "noImplicitAny": true, "module": "es6", "target": "es5", "jsx": "react", "allowJs": true } }
const path = require("path"); module.exports = { entry: "./src/index.ts", module: { rules: [ { test: /\.tsx?$/, use: "ts-loader", exclude: /node_modules/ } ] }, resolve: { extensions: [".tsx", ".ts", ".js"] }, output: { filename: "bundle.js", path: path.resolve(__dirname, "dist") } };
npm install --save-dev @types/lodash
https://www.webpackjs.com/configuration/resolve/
)stats.json
分析打包結果(bundle analyze)你能夠關注個人同名公衆號【前端森林】,這裏我會按期發一些大前端相關的前沿文章和平常開發過程當中的實戰總結。固然,我也是開源社區的積極貢獻者,github地址https://github.com/Jack-cool
,歡迎star!!!