本文也是屢次學習webpack積累下來的知識點,一直在雲筆記裏。javascript
webpack構建流程
從啓動webpack構建到輸出結果經歷了一系列過程,它們是:css
須要注意的是,在構建生命週期中有一系列插件在合適的時機作了合適的事情,好比UglifyJsPlugin會在loader轉換遞歸完後對結果再使用UglifyJs壓縮覆蓋以前的結果html
配置webpack就是在配置一個node的模塊module.exports用於webpack來讀取vue
webpack的優勢:java
webapck的缺點:node
Rollupreact
webpack-dev-server是一個啓動服務的程序,能夠自動去,讀取webpack.config.js 的文件。執行webpack-dev-server來啓動,就能夠來自動刷新頁面,內部的原理是express的啓服務,用webSocket來通知瀏覽器。jquery
module.exports = { context: path.resolve(__dirname, './app'), entry:'./main.js', output: { filename: 'bundle.js', path: path.resolve(__dirname, './dist') } }
也能夠動態的配置,就是返回函數,能夠同步也能夠異步webpack
同步 entry:()=>{ return { a: './pages/a', b:'./pages/a' } }, 異步 entry:new Promise((resolve)=>{ resolve({ a: '/a.js', b:'/b.js' }) }),
output是一個對象,裏面有配置項git
能夠取一下值:
rules: [{ test: '/\.js$/',//命中文件 use: ['babel-loader?cacheDirectory'],//指定loader 解析是從後向前 include: path.resolve(__dirname, 'src'),//包括 exclude: path.resolve(__dirname, 'node_modules')//排除 },{ test: /.vue$/, use: ['vue-loader'] }] vue須要使用vue-loader
noParse: /jquery|chartjs/ //通常是正則 被忽略的文件不該該有import/requie/define等
resolve: { alias: { components : './src/components' }, mainFields: ['jsnext:main', 'browser', 'main'],//jsnext 是支持ES6的模式進入 extension: ['.ts','.js','.json'],//後綴列表 //延長;延期 modules: ['./src/components', 'node_modules'],//指定本地解析 import 'button' enforceExtension: true,//開啓必須帶後綴 },
devServer:{ hot: true,//啓用 webpack 的模塊熱替換特性 inline: true,//fasle是 iframe 模式 historyApiFallback: true,//spa history的模式, 任何請求都會返回index.html 也可使用重寫rewrites來詳細配置 contentBase:[path.join(__dirname, "public"), path.join(__dirname, "assets")],//提供靜態文件 string fales關閉 array host:'0.0.0.0',//服務器外部可訪問 port: '9090',//端口 disableHostCheck: true,//關閉host,devServer默認是接受本地請求配合host使用 https:true,//開啓https或者本身導入證書 compress: true,//默認false開啓gzip壓縮 壓縮 open: true,//打開瀏覽器 proxy:{ // credentials 證書 設置成include,表示容許跨越傳遞cookie "/api": "http://localhost:3000" } },
target:'web',//構建到的環境好比node web webworker等 devtool:'source-map',// 能夠設置爲false watch:true,//監聽文件,默認是關閉的devserer默認打開 externals:{ jquery: 'jQuery' }, resolveLoader:{ //加載本地loader },
file-loader 將js和css中的圖片等替換成正確的地址,輸出在文件中
url-loader 能夠將文件的內容通過base64編碼後注入js或者css中,可是要限制大小
{ exclude: [ /\.(js|jsx)(\?.*)?$/, /\.(css|scss)$/, /\.json$/, /\.bmp$/, /\.jpe?g$/, /\.png$/, ], loader: require.resolve('file-loader'), options: { name: 'static/media/[name].[hash:8].[ext]', }, }, { test: [/\.bmp$/, /\.jpe?g$/, /\.png$/], loader: require.resolve('url-loader'), options: { limit: 10000, name: 'static/media/[name].[hash:8].[ext]', }, },
raw-loader 和 svg-inline-loader 能夠把svg內嵌到網頁,把svg當圖片用,能夠用上面的。
以方便在瀏覽器經過代碼調試
devtool有不少的取值,由一下6個關鍵字隨意組合而成
module:來時loader的source map 被簡單處理成每行一個模塊
source-map: 原始代碼 最好的sourcemap質量有完整的結果,可是會很慢
eval和.map文件都是sourcemap實現的不一樣方式,雖然大部分sourcemap的實現是經過產生.map文件, 但並不表示只能經過.map文件實現。下面是eval模式後產生的模塊代碼
cheap關鍵字的配置中只有行內,列信息指的是代碼的不包含原始代碼的列信息
npm 能夠運行node__modules裏安裝的程序 能夠縮短命令
javascript 用eslint 用.eslintrc json 文件配置 eslint-loader enforce: 'pre'
ts 用TSlint tslint.json 配置 tslint-loader
css stylelint 用.stylelintrc 配置 StyleLintPlugin 插件來處理
代碼檢測會變慢構建速度,能夠配置IDE來檢測,同時配置git Hook在代碼提交時檢測
var config = require('../config') var compiler = webpack(webpackConfig)
調用compiler的watch能夠監聽文件變化
require('webpack-dev-middleware')是express的一個插件
var app = express() var compiler = webpack(webpackConfig) var devMiddleware = require('webpack-dev-middleware')(compiler, { publicPath: webpackConfig.output.publicPath, stats: { colors: true, chunks: false } })
可是不支持hot模式須要require('webpack-hot-middleware')
app.use(hotMiddleware)
代理使用
var proxyMiddleware = require('http-proxy-middleware')
modules: [path.resolve(__dirname, 'node_modules')]
alias: { react: path.resolve(__dirname, './node_modules/react/dist/react.min.js') },
noParse: /jquery|chartjs/ //通常是正則 被忽略的文件不該該有import/require/define等
工程就把 react react-dom prop-types classnames mobx mobx-react lodash moment polyfill 等打進來 可使用 [npm version]_dll.js 用 npm version 的話只要 version 一改變咱們會從新打包,好比升級了 react ,咱們就會 version +,就會從新打包。
先建立一個webpack.config.dll.js
執行webpack --config ./webpack.config.dll.js把須要dll的文件輸出到dist/dll
const webpack = require('webpack'); const path = require('path'); const {version} = require('./package.json'); module.exports = { entry: { 'react': [ 'react', 'react-dom', 'prop-types', 'classnames', 'lodash', 'moment' ] }, output: { path: path.join(__dirname, 'dist/dll'), filename: `[name].${version}.js`, library: 'dll_[name]', publicPath: '/dist/dll/' }, plugins: [ new webpack.DllPlugin({ path: path.join(__dirname, 'dist/dll/', '[name].manifest.json'), name: 'dll_[name]' }) ] };
在webapck配置文件中使用
new webpack.DllReferencePlugin({ context: __dirname, // 在這裏引入 manifest 文件 manifest: require('./dist/dll/react.manifest.json') }), //把js插入到html文件中 new AddAssetHtmlPlugin({ filepath: require.resolve(`./dist/dll/${getDLLFileName()}`), outputPath: 'dll', includeSourcemap: false, hash: true, publicPath: '/dist/dll/' })
原理就是提早構建,緩存起來,用window的全局變量來取。
{ test: /\.(js|jsx)$/,//命中文件 use: 'happypack/loader?id=js',//指定loader include: path.resolve(__dirname, 'src'),//包括 exclude: path.resolve(__dirname, 'node_modules')//排除 }, { test: /\.(css|scss)$/, loader: 'happypack/loader?id=css' }
new HappyPack({ id: 'js', threadPool: happyThreadPool, loaders: [{ path: 'babel-loader', query: { cacheDirectory: true } }] }), new HappyPack({ id: 'css', threadPool: happyThreadPool, loaders: ['style-loader','css-loader', 'sass-loader'] }),
原理:happypack 的原理是讓loader能夠多進程去處理文件,css和js,圖片和文件支持很差
Devtool 使用 開發用cheap-module-eval-source-map 那個最快
區分環境
if (isDev) { config.plugins.push(new webpack.NamedModulesPlugin()); //顯示模塊更新名字 config.plugins.push(new webpack.HotModuleReplacementPlugin()); //hot更新插件 config.devServer = { hot: true, contentBase: './', historyApiFallback: { index: "/build/index.html" }, publicPath: '/build/', host: '0.0.0.0' }; config.devtool = 'eval'; } else { config.plugins.push(new webpack.optimize.UglifyJsPlugin({ // 開啓壓縮 cache: true, parallel: true, compress: { warning: fasle,// 是否刪除一個警告信息fasle刪除 drop_console: true,//刪除console collapse_vars: true,是否內嵌雖然已定義可是隻用到一次的變量。 reduce_vars: true ,提取屢次的變量 }, output: { comments: false,//是否刪除註釋,默認是不刪除,設置爲false,刪除全部的註釋 beautify: fasle, //保留空格和製表符 建議false關閉,最緊湊的輸出 } })); config.devtool = '#source-map'; }
八、壓縮css 在css-loader開啓minimize選項 和 提取css到單獨的文件
{ test: /\.(css|less)$/, loader: ExtractTextPlugin.extract({ fallback: 'style-loader', use: 'happypack/loader?id=css' }) },
new ExtractTextPlugin('css/[name].[contenthash:8].css')
九、在webapck中接入cdn
十、Tree Shaking 去除重複代碼 ,webapck沒有程序流分析,避免不了babel產生的反作用
十一、以模塊化來引入
// 原來的引入方式 import {debounce} from 'lodash'; //按模塊化的引入方式 import debounce from 'lodash/debounce';
十二、使用異步的模塊加載
require.ensure來設置哪些模塊須要異步加載,webpack會將它打包到一個獨立的chunk中
$('.bg-input').click(() => { console.log('clicked, loading async.js') require.ensure([], require => { require('./components/async2').log(); require('./components/async1').log(); console.log('loading async.js done'); }); });
import(*) 是新的按需加載,在react-router4中不能使用require.ensure要使用getAsyncComponent函數
component = { getAsyncComponent(() => { import('./pages/login') }) }
1三、Scope Hoisting 做用域提高 webpack3的新功能
使用ModuleConcatenationPlugin插件來加快JS執行速度
這是webpack3的新特性(Scope Hoisting),實際上是借鑑了Rollup打包工具來的,它將一些有聯繫的模塊,放到一個閉包函數裏面去,經過減小閉包函數數量從而加快JS的執行速度
new webpack.optimize.ModuleConcatenationPlugin({ })
原理:原理其實很簡單,分析模塊之間的依賴關係,儘量將被打散的模塊合併到一個函數中,前提是不能形成代碼冗餘,源碼必須採用es6語句。
1四、提取公共代碼
使用CommonsChunkPlugin提取公共的模塊,能夠減小文件體積,也有助於瀏覽器層的文件緩存,仍是比較推薦的,那個是在最後打包的時候用的。
// 提取公共模塊文件 new webpack.optimize.CommonsChunkPlugin({ chunks: ['home', 'detail'], // 開發環境下須要使用熱更新替換,而此時common用chunkhash會出錯,能夠直接不用hash filename: '[name].js' + (isProduction ? '?[chunkhash:8]' : ''), name: 'common' }), // 切合公共模塊的提取規則,有時後你須要明確指定默認放到公共文件的模塊 // 文件入口配置 entry: { home: './src/js/home', detail: './src/js/detail', // 提取jquery入公共文件 common: ['jquery', 'react', 'react-dom'] }, entry: {index:'./src/index.js',vendor: ['react','react-dom','react-router']}, new webpack.optimize.CommonsChunkPlugin({ name: "vendor", filename: "vendor.js", }),
1五、prepack
利用抽象語法樹(AST)來分析源碼,過來問題大大,不建議使用
const PrepackWebpackPlugin = require('prepack-webapck-plugin').default; module.exports = { plugins: [ new PrepackWebpackPlugin() ] }
1六、可視化的輸出分析 Analyse webpack-bundle-analyzer
用 Webpack 構建接入 Service Workers 的離線應用要解決的關鍵問題在於如何生成上面提到的 sw.js 文件, 而且sw.js文件中的 cacheFileList 變量,表明須要被緩存文件的 URL 列表,須要根據輸出文件列表所對應的 URL 來決定,而不是像上面那樣寫成靜態值。
假如構建輸出的文件目錄結構爲:
├── app_4c3e186f.js ├── app_7cc98ad0.css └── index.html
那麼 sw.js 文件中 cacheFileList 的值應該是:
var cacheFileList = [ '/index.html', 'app_4c3e186f.js', 'app_7cc98ad0.css' ];
Webpack 沒有原生功能能完成以上要求,幸虧龐大的社區中已經有人爲咱們作好了一個插件 serviceworker-webpack-plugin 能夠方便的解決以上問題。 使用該插件後的 Webpack 配置以下:
const path = require('path'); const ExtractTextPlugin = require('extract-text-webpack-plugin'); const { WebPlugin } = require('web-webpack-plugin'); const ServiceWorkerWebpackPlugin = require('serviceworker-webpack-plugin'); module.exports = { entry: { app: './main.js'// Chunk app 的 JS 執行入口文件 }, output: { filename: '[name].js', publicPath: '', }, module: { rules: [ { test: /\.css/,// 增長對 CSS 文件的支持 // 提取出 Chunk 中的 CSS 代碼到單獨的文件中 use: ExtractTextPlugin.extract({ use: ['css-loader'] // 壓縮 CSS 代碼 }), }, ] }, plugins: [ // 一個 WebPlugin 對應一個 HTML 文件 new WebPlugin({ template: './template.html', // HTML 模版文件所在的文件路徑 filename: 'index.html' // 輸出的 HTML 的文件名稱 }), new ExtractTextPlugin({ filename: `[name].css`,// 給輸出的 CSS 文件名稱加上 Hash 值 }), new ServiceWorkerWebpackPlugin({ // 自定義的 sw.js 文件所在路徑 // ServiceWorkerWebpackPlugin 會把文件列表注入到生成的 sw.js 中 entry: path.join(__dirname, 'sw.js'), }), ], devServer: { // Service Workers 依賴 HTTPS,使用 DevServer 提供的 HTTPS 功能。 https: true, } };
├── pages │ ├── index │ │ ├── index.css // 該頁面單獨須要的 CSS 樣式 │ │ └── index.js // 該頁面的入口文件 │ └── login │ ├── index.css │ └── index.js ├── common.css // 全部頁面都須要的公共 CSS 樣式 ├── google_analytics.js ├── template.html └── webpack.config.js
AutoWebPlugin 強制性的規定了項目部分的目錄結構,在pages下,每個文件夾就是一個目錄。經過插件自動生成手動須要配置的兩個html插件。
const { AutoWebPlugin } = require('web-webpack-plugin'); // 使用本文的主角 AutoWebPlugin,自動尋找 pages 目錄下的全部目錄,把每個目錄當作一個單頁應用 const autoWebPlugin = new AutoWebPlugin('pages', { template: './template.html', // HTML 模版文件所在的文件路徑 postEntrys: ['./common.css'],// 全部頁面都依賴這份通用的 CSS 樣式文件 // 提取出全部頁面公共的代碼 commonsChunk: { name: 'common',// 提取出公共代碼 Chunk 的名稱 }, }); module.exports = { // AutoWebPlugin 會爲尋找到的全部單頁應用,生成對應的入口配置, // autoWebPlugin.entry 方法能夠獲取到全部由 autoWebPlugin 生成的入口配置 entry: autoWebPlugin.entry({ // 這裏能夠加入你額外須要的 Chunk 入口 }), plugins: [ autoWebPlugin, ], };
template.html 模版文件以下:
<html> <head> <meta charset="UTF-8"> <!--在這注入該頁面所依賴但沒有手動導入的 CSS--> <!--STYLE--> <!--注入 google_analytics 中的 JS 代碼--> <script src="./google_analytics.js?_inline"></script> <!--異步加載 Disqus 評論--> <script src="https://dive-into-webpack.disqus.com/embed.js" async></script> </head> <body> <div id="app"></div> <!--在這注入該頁面所依賴但沒有手動導入的 JavaScript--> <!--SCRIPT--> <!--Disqus 評論容器--> <div id="disqus_thread"></div> </body> </html>
和 在模版中生成
// 引入插件 const HTMLWebpackPlugin = require("html-webpack-plugin"); // 引入多頁面文件列表 const { HTMLDirs } = require("./config"); // 經過 html-webpack-plugin 生成的 HTML 集合 let HTMLPlugins = []; // 入口文件集合 let Entries = {} // 生成多頁面的集合 HTMLDirs.forEach((page) => { const htmlPlugin = new HTMLWebpackPlugin({ filename: `${page}.html`, template: path.resolve(__dirname, `../app/html/${page}.html`), chunks: [page, 'commons'], }); HTMLPlugins.push(htmlPlugin); Entries[page] = path.resolve(__dirname, `../app/js/${page}.js`); })
構建服務端渲染
服務端渲染的代碼要運行在nodejs環境,和瀏覽器不一樣的是,服務端渲染代碼須要採用commonjs規範同時不該該包含除js以外的文件好比css。webpack配置以下:
module.exports = { target: 'node', entry: { 'server_render': './src/server_render', }, output: { filename: './dist/server/[name].js', libraryTarget: 'commonjs2', }, module: { rules: [ { test: /\.js$/, loader: 'babel-loader', }, { test: /\.(scss|css|pdf)$/, loader: 'ignore-loader', }, ] }, };
其中幾個關鍵的地方在於:
target: 'node' 指明構建出的代碼是要運行在node環境裏
libraryTarget: 'commonjs2' 指明輸出的代碼要是commonjs規範
{test: /.(scss|css|pdf)$/,loader: 'ignore-loader'} 是爲了防止不能在node裏執行服務端渲染也用不上的文件被打包進去。
若是你的擴展是想對一個個單獨的文件進行轉換那麼就編寫loader剩下的都是plugin
其中對文件進行轉換能夠是像:
編寫loader很是簡單,以comment-require-loader爲例:
module.exports = function (content) { return replace(content); };
loader的入口須要導出一個函數,這個函數要乾的事情就是轉換一個文件的內容。
函數接收的參數content是一個文件在轉換前的字符串形式內容,須要返回一個新的字符串形式內容做爲轉換後的結果,全部經過模塊化倒入的文件都會通過loader。從這裏能夠看出loader只能處理一個個單獨的文件而不能處理代碼塊
class EndWebpackPlugin {
constructor(doneCallback, failCallback) { this.doneCallback = doneCallback; this.failCallback = failCallback; } apply(compiler) { 彙編者; 編輯者; 編纂者 // 監聽webpack生命週期裏的事件,作相應的處理 compiler.plugin('done', (stats) => { this.doneCallback(stats); }); compiler.plugin('failed', (err) => { this.failCallback(err); }); }
}
module.exports = EndWebpackPlugin;
loader的入口須要導出一個class, 在new EndWebpackPlugin()的時候經過構造函數傳入這個插件須要的參數,在webpack啓動的時候會先實例化plugin再調用plugin的apply方法,插件須要在apply函數裏監聽webpack生命週期裏的事件,作相應的處理。
webpack plugin 裏有2個核心概念:
Compiler: 從webpack啓動到推出只存在一個Compiler,Compiler存放着webpack配置
Compilation: 因爲webpack的監聽文件變化自動編譯機制,Compilation表明一次編譯。
Compiler 和 Compilation 都會廣播一系列事件。
webpack生命週期裏有很是多的事件能夠在event-hooks和Compilation裏查到
yaml-loader:加載 YAML 文件。
markdown-loader:把 Markdown 文件轉換成 HTML。
coffee-loader:把 CoffeeScript 轉換成 JavaScript。
stylus-loader:把 Stylus 代碼轉換成 CSS 代碼。
coverjs-loader:計算測試覆蓋率。
ui-component-loader:按需加載 UI 組件庫,例如在使用 antd UI 組件庫時,不會由於只用到了 Button 組件而打包進全部的組件。
ignore-plugin:用於忽略部分文件。
hot-module-replacement-plugin:開啓模塊熱替換功能。
web-webpack-plugin:方便的爲單頁應用輸出 HTML,比 html-webpack-plugin 好用。