1. 使用webpack
webpack命令使用
webpack --help (webpack -h) 查看全部的命令
webpack-v
打包命令 webpack [] 不適用webpack配置文件的時候
使用webpack配置文件, 直接webpack
--config 指定配置文件 (默認配置文件名稱 webpack.config.js或者 webpackfile.js)
Basic Options
--entry 指定入口文件
--watch -w 檢測文件系統的變化
--debug -d 打開調試狀態
--devtool 生成sourceMap
--progress 進度條顯示
-d 簡寫 --mode development
Module Options
Output Options
Advanced Options 高級選項
Resolved Options 解析選項
Optimization Options 優化選項
Stats Option 狀態選項 (打包出來樣式的選項)
使用webpack配置(配合node npm使用)
不一樣的配置文件, 開發環境, 生產環境, 測試環境, 打包給第三方使用的
第三方的腳手架vue-cli
交互式的初始化一個項目
項目遷移v1->v2
# wepbpack-cli的使用
webpack-cli init webpack-addons-demo
# 項目遷移
webpack-cli migrate <config> ## 只會升級配置文件, package.json裏面的文件 須要手機升級
2. 直接使用webpack命名, 使用默認文件或者默認配置
// app.js
import sum from './sum'
conosle.log(sum(1,2))
// sum.js
export default function sum(a, b) {
return a + b;
}
// 打包命令: webpack app.js --output-path=dist --output-filename=bundle.js --mode development
// 指定配置文件 webpack --config webpack.config.dev.js
3. 編譯ES6/7
babel-loader
## 安裝最新版本loader
npm install
babel-loader@8.0.0-beta @babel/core --save-dev ## 安裝最新preset npm install @babel/preset-env --save-dev npm install babel-loader babel-core --save-dev npm install babel-preset-env --save-dev 指定規範的版本, 只是針對語法 es2015 es2016 es2017 env 包括2015~2017, 以及latest 用的比較多 業內自定義的babel-preset-react babel-preset-stage 0 ~3 表示規範組尚未正式發佈階段的 babel-presets - options - target 當須要編譯的時候, 會根據指定的target來選擇那些語法進行編譯, 那些語法不進行編譯 target.browsers 指定瀏覽器環境 target.browsers: 'last 2 versions' 主流瀏覽器的最後兩個版本 target.browsers: '> 1%' 大於全球瀏覽器佔有率1%的瀏覽器 數據來源是 browserlist中, can i use中 { test: /\.js$/, use: { // use: 'babel-loader' //能夠直接是一個字符串 loader: 'babel-loader', options: { // 指定preset presets: [['env', { // 告訴babel, 當須要編譯的時候, 會根據指定的target來選擇那些語法進行編譯, 那些語法不進行編譯 targets: { browsers: ['> 1%', 'last 2 versions'], // chrome: '52' // 一些新語法瀏覽器直接支持 不會被轉換 } }]] } }, exclude: '/node-modules/' } // 當同時指定'> 1%', 'last 2 versions'的時候, 箭頭函數會被轉化, const, let等被轉化, set不會被轉化, num**2 轉成了Math.pow // 將targets換成 chrome: '52', 轉化後代碼基本和原生代碼同樣 target.node 指定node環境 babel-polyfill插件和babel-runtime-transform插件 針對一些方法好比數組的map, includes, Set並無被babel處理, 可是在一些低版本的瀏覽器中這些方法並無被實現, 因此須要藉助這兩個插件 babel-preset 只是針對語法, 而這兩個插件針對函數和方法 generator Set Map Array.from Array.prototype.includes 上述方法都沒有被babel處理, 因此就須要藉助babel的插件進行處理 babel-polyfill 墊片, 瀏覽器之間標準實現的方式不同,保持瀏覽器之間一樣的API 全局墊片 (只要引入, 在全局範圍內整個瀏覽器範圍內, 能夠對實現的API進行調用) 至關於對全局變量的一個污染, 爲開發應用而準備的 (在業務中使用, 而不是框架好比vue) 使用: npm install babel-polyfill --save 真實項目中的依賴 因此是--save 在項目中使用 import 'babel-polyfill' babel-runtime-transform 局部墊片 爲開發框架而準備的, 不會污染全局變量, 會在局部的方法裏面新增長變量和方法 優點: 當在代碼中使用它的時候, 項目中的其餘函數,若是使用es6/7方法, 會將每個引用到的方法打包到單獨的文件中去的; 若是使用了runtime-transform, 將其做爲一個獨立的總體單獨打包進去, 至關於文件之間多餘的代碼就不會再有了 npm install babel-plugin-transform-runtime --save-dev npm install babel-runtime --save .babelrc 在裏面配置和babel插件相關的內容 // app.js import sum from './sum' const func = () => { console.log('hello babel') } func() const arr = [1, 2, 3, 4, 5, 4, 3, 2, 1] const arrb = arr.map(item => item * 2) // 下面的語句 不會通過runtime編譯 arr.includes(5); // 會通過runtime編譯 可是沒有exports 使用的時候報錯 console.log('SetB', new Set(arrb)) /* function* gen() { yield 1 } */ sum(1, 2) // .babelrc { "presets": [["env", { "targets": { "browsers": ["> 1%", "last 2 versions"] } }]], "plugins": [ "transform-runtime" ] } // 1. 當plugins爲空的時候, 上面的代碼會完整運行, 都不會被轉義 // 2. 添加generator函數的時候, 會報錯找不到 regenerator // 3. 添插件的時候 includes不會編譯, Set, generator會編譯, 可是報錯$export is not a function // 4. 屏蔽插件plugins, 使用polyfill, 完美運行全部新屬性, 可是打包文件很大, 達到了471Kb 實際開發中如何選擇 若是是應用開發, 只須要配置preset, 若是要使用es6/7新語法, 使用polyfill 若是是開發UI庫, 框架, 使用runtime 4. 編譯TypeScript JS的超集 tslang.cn 來自於微軟 官方推薦: npm install typescript ts-loader --save-dev 第三方loader: npm install typescript awesome-typescript-loader --save-dev 配置: tsconfig.json ## 經常使用配置選項 compilerOptions:告訴編譯器經常使用的配置選項, 好比 容許js 模塊化方式指定:commonjs 指定輸出路徑等 compilerOptions.module: 模塊化方式指定 compilerOptions.target: 編譯以後的文件在什麼環境下運行的 (相似將語言編譯到什麼程度) compilerOptions.typeRoots: [ "./node_modules/@type", // 默認安裝npm install @types/lodash時路徑 "./typings/modules", // 使用typings安裝的聲明文件路徑 ] 指定types聲明文件所在的地址 include: 給出一系列的文件路徑, 表示須要編譯的文件 exclude: 忽略的文件 allowJs: 是否容許js的語法 安裝聲明文件.這樣在編譯的時候就會給出警告錯誤, 告訴咱們傳遞的參數類型有錯誤 npm install @types/lodash npm install @types/vue 或者使用typings安裝types聲明文件, 使用compilerOptions.typeRoots 5. 提取公用代碼 減小冗餘代碼(每個頁面都會存在公共代碼, 形成帶寬浪費) 提升用戶的加載速度(只加載頁面所須要的依賴, 其餘頁面在加載的時候, 公共代碼已經加載好了) CommonChunkPlugin (webpack.optimize.CommonChunkPlugin) // 針對webpack3 { plugins: [ new webpack.optimize.CommonChunkPlugin({ name: String | Array, // 表示chunk的名稱 ? filename: String, // 公用代碼打包的文件名稱 minChunks: Number|function|Infinity // 數字表示爲須要提取的公用代碼出現的次數(最小是多少, 好比出現兩次以上就提取到公用代碼), Infinity 表示不講任何的模塊打包進去, 函數的話表示自定義邏輯 chunks: 表示指定提取代碼的範圍, 須要在哪幾個代碼快中提取公用代碼 children: 是否是在entry的子模塊中 仍是在全部模塊中查找依賴 deepChildren async: 建立一個異步的公共代碼流 }) ] } // webpack4 optimization: { splitChunks: { chunks: 'all', // async(默認, 只會提取異步加載模塊的公共代碼), initial(提取初始入口模塊的公共代碼), all(同時提取前二者), minSize: 0, // 30000, // 大於30K會被抽離到公共模塊 minChunks: 2, // 模塊出現一次就會被抽離到公共模塊中, 若是是1的話, 表示將全部的模塊都提走, 針對pageA中, 若是隻有本身引用jQuery, 那麼會生成jQuery-vendor.js 的打包文件 maxAsyncRequests: 5, // 異步模塊, 一次最多隻能加載5個, maxInitialRequests: 3, // 入口模塊最多隻能加載3個 name: true } } 場景 單頁應用 單頁應用 + 第三方依賴 多頁應用 + 第三方依賴 + webpack生成代碼 (webpack內置函數) 針對單入口的commonChunksPlugin = 並無將公共部分打包, 只有針對多入口才會 多入口文件的時候 entry: { pageA: path.resolve(__dirname, 'src/cmp', 'pageA'), pageB: path.resolve(__dirname, 'src/cmp', 'pageB') // vendor: ['lodash'] }, // webpack3 plugins: [ new webpack.optimize.CommonPluginsChun({ name: 'vendor', minChunks: Infinity }) // 公共模塊打包的名字爲vendor, entry中也有vendor, 因此會將webpack生成代碼以及lodash打包進vendor中 ] // webpack4 splitChunks: { chunks: 'all', // async(默認, 只會提取異步加載模塊的公共代碼), initial(提取初始入口模塊的公共代碼), all(同時提取前二者), minSize: 30000, // 大於30K會被抽離到公共模塊 // minChunks: 2, // 模塊出現兩次次就會被抽離到公共模塊中 minChunks: Infinity, // 不須要在任何的地方重複 maxAsyncRequests: 5, // 異步模塊, 一次最多隻能加載5個, maxInitialRequests: 3, // 入口模塊最多隻能加載3個 // name: 'common' // 打包出來公共模塊的名稱 name: 'vendor' // 打包出來公共模塊的名稱 } // 1. 會將pageA, pageB中 公共使用的模塊打包成進common.chunk.js (name:'common'的時候), 公共模塊中包括webpack生成的代碼 // 2. lodash只在pageA中使用, 次數爲1, 可是minChunks: 2, 因此lodash只會被打包進pageA中 // 3. 在entry中添加 vendor: ['lodash'] 將公共庫lodash單獨打包, 在webpack4中將其打包進了公共common.chunk中, vendor中只有對lodash的引用 // 4. 若是想將lodash和webpack運行生成時代碼以及公共代碼打包到一塊兒, minChunks改爲Infinity, name:vendor, 將全部生成的文件引用都放到vendor中了 // 5. 保持第三方代碼的純淨, 即將第三方代碼和webpack分離開, webapck3添加plugins, webpack4添加runtimeChunk配置 // webpack3 new webpack.optimize.CommonPluginsChun({ name: 'manifest', minChunks: Infinity }) // 發現vendor和manifest處大部分代碼是同樣的能夠, 能夠改爲 new webpack.optimize.CommonPluginsChun({ names: ['vendor','manifest'], minChunks: Infinity }) // webpack4 runtimeChunk: { name: 'manifest' }, // 結果是: 將webpack生成的代碼打包到manifest中, 將lodash打包進vendor中, 將引用次數超過兩次的打包進vendor中 6. 代碼分割和懶加載 經過代碼分割和懶加載, 讓用戶在儘量的下載時間內加載想要的頁面, 只看一個頁面的時候, 下載全部的代碼, 帶寬浪費; 在webpack中, 代碼分割和懶加載是一個概念, webpack會自動分割代碼, 而後再把須要的代碼加載進來, 不是經過配置來實現的, 經過改變寫代碼的方式來實現的, 當依賴一個模塊的時候, 告訴webpack咱們是須要懶加載或者代碼切分, 經過兩種方式來實現 webpack.methods require.ensure() 接收四個參數 第一個參數dependencies, 加載進來的代碼並不會執行, 在callback中引入, 這個時候纔會去執行, 第三個參數errorBack, 第四個參數chunkName 若是瀏覽器不支持promise, 須要添加墊片 require.include 只有一個參數, 只引入進來, 但不執行 當兩個子模塊都引入了第三個模塊, 能夠將第三個模塊放入父模塊中, 這樣動態加載子模塊的時候, 父模塊已經有了第三方模塊, 不會在多餘加載; 好比subPageA, subPageB都引入了moduleA, 可是moduleA不會被打包進父依賴, 因此可使用include ES2015 loader spec (動態import) stage-3 早起system.import 後來import方式 返回一個Promise import().then webpack import function 經過註釋的方式來解決動態的chunkName以及加載模式 import( /*webpackChunkName: async-chunk-name*/ /*webpackMode: lazy*/ moduleName ) 代碼分割的場景 分離業務代碼和第三方依賴 (提取公共代碼中有涉及) 分離業務代碼 和 業務公共代碼 和 第三方依賴; 相比於上一個,將業務代碼拆成兩部分 分離首次加載 和 訪問後加載的代碼 (訪問速度優化相關的) - LazyLoad - 提升首屏加載速度 // 0. 單入口pageA, 不作任何的優化 直接引入 subPageA, subPageB, lodash 會發現pageA很是大 // 1. 異步引入, 將lodash打包到vendor中 require.ensure('lodash', require => { const _ = require('lodash') _.join([1, 2, 3], 4) console.log(_) }, 'vendor') // 2. pageA.js中修改 if (page === 'subPageA') { // require([]) 參數是空數組的話, 裏面的require的包仍是會被異步打包 require.ensure(['./subPageA'], require => { // 若是不require的話, 那麼就不會執行subPageA中的代碼塊 const subPageA = require('./subPageA') console.log(subPageA) }, 'subPageA') } else if (page === 'subPageB') { require.ensure(['./subPageB'], require => { const subPageB = require('./subPageB') console.log(subPageB) }, 'subPageB') } // 結果: moduleA分別在打包好的文件 subPageA.chunk.js 和 subPageB.chunk.js中, 公共部分moduleA沒有被提取出來 // 3. 單entry有上述公共代碼的狀況的話, 使用inlcude的狀況處理, 將module在父模塊pageA.js提早引入, 可是並不運行 require.include('./moduleA') // 結果: moduleA被打包進入了pageA.bundle.js中, 這樣就完成了代碼分割 // --- import 方案 ------------- /* 坑: import 只有在stage-0 或者 syntax-dynamic-import yarn add babel-preset-stage-0 babel-plugin-syntax-dynamic-import --dev .babelrc { "presets": ["stage-0"], "plugins": ["syntax-dynamic-import"] } 上述兩種狀況只使用一種便可 */ // 在import的時候 代碼實際上已經執行了 if (page) { import( /* webpackChunkName: "subPageA" */ /* webpackMode: "lazy" */ './subPageC' ).then(subPageC => { console.log(subPageC) }) } else { import( /* webpackChunkName: 'subPageD' */ /* webpackMode: "lazy" */ './subPageD' ) } async 在代碼分割中如何使用, 即結合commonChunkPlugin // webpack.plugin.lazy.cmp.js entry: { pageA: path.resolve(__dirname, 'src/lazy_cmp', 'pageA'), pageB: path.resolve(__dirname, 'src/lazy', 'pageB'), vendor: ['lodash'] } // webpack3 plugins: [ new wepback.optimize.CommonsChunkPlugin({ // async 指定爲true表示異步模塊, 或者指定爲 異步模塊提取後的名稱 async: 'async-common', children: true, // 表示不只僅是兩個入口頁面之間, 並且仍是兩個頁面之間的子依賴中去尋找 minChunks: 2 }), new wepback.optimize.CommonsChunkPlugin({ // lodash打包進入vendor中, manifest是webpack運行時代碼 names: ['vendor', 'manifest'], minChunks: Infinity }) ] // webpack4 optimization: { // webpack runtime 代碼 runtimeChunk: { name: 'manifest' }, // 公共模塊提取 splitChunks: { chunks: 'all', // async(默認, 只會提取異步加載模塊的公共代碼), initial(提取初始入口模塊的公共代碼), all(同時提取前二者), minSize: 30000, // 大於30K會被抽離到公共模塊 // minChunks: 2, // 模塊出現兩次次就會被抽離到公共模塊中 minChunks: Infinity, // 不須要在任何的地方重複 maxAsyncRequests: 5, // 異步模塊, 一次最多隻能加載5個, maxInitialRequests: 3, // 入口模塊最多隻能加載3個 name: 'vendor' // 打包出來公共模塊的名稱 } } // pageA.js import _ from 'lodash' / 1. 這裏再也不使用include, 由於會和pageA打包到一塊兒, 這裏的目的是 將其異步單獨提取出來 // require.include('./moduleA') const page = 'subPageA' // 在pageB中, 這裏page='subPageB', 其他同樣 if (page) { import( /* webpackChunkName: "subPageA" */ /* webpackMode: "lazy" */ './subPageA' ).then(subPageA => { console.log(subPageA) }) } else { import( /* webpackChunkName: 'subPageB' */ /* webpackMode: "lazy" */ './subPageB' ) } // 2. webpack3 結果: 將異步打包結果中subPageA和subPageB中的公共模塊moduleA, 單獨的提取到了async-common-pageA.chunk.js中 這裏比較坑的困惑: commonsChunkPlugin參數說的不是很明確, 好比async, children, deepChildren, minChunk, 他們之間是有依賴忽視關係的 // 3. webpack4 結果: chunks:all, 結果是將屢次引用的公共模塊moduleA, lodash提取到了vendor.chunk中, 其他的和webpack3同樣, 生成打包文件pageA.chunk, pageB.chunk(入口文件), subPageA.chunk, subPageB.chunk(異步單獨提取), manifest.chunk(webpack-runtime單獨提取) 5. 處理CSS 每個模塊都有本身的css文件, 在使用的時候將css樣式引入 如何在webpack中引入css style-loader 在頁面中建立style標籤, 標籤裏面的內容就是css內容 style-loader/url style-loader/useable css-loader 如何讓js能夠import一個css文件, 包裝一層, 讓js能夠引入css文件 // index.js import './css/base.css' // webpack.config.style.js { test: /\.css$/, use: [ { loader: 'style-loader' }, { loader: 'css-loader' } ] } // 將打包後的文件引入到index.html中 // 1. 結果: 在html中生成了style標籤, 將base.css標籤中的樣式放到了style標籤中 // 2. 生成link標籤的形式 (不過用的比較少) 注意publicPath配置 use: [ { loader: 'style-loader/url' // loader: 'style-loader/useable' }, { loader: 'file-loader' } ] // 結果: style-loader/url 單獨生成一份css文件 , 可是引入多個文件的時候, 會生成多個link標籤, 會形成越多的網路請求 //3. style-loader/useable import base from 'base.css' import common from 'common.css' var flag = false; setInterval(function() { if(flag) { base.use() } else { base.ununse() } flag = !flag; }, 2000) // base.use() 樣式插入到style標籤中 // common.unuse() // 控制樣式不被引用 // 結果: 沒過2000ms, 頁面中樣式循環引用和刪除 6. StyleLoader 配置 insertAt (插入位置) insertInto(插入到DOM) singleton (是否只使用一個style標籤) 當css模塊比較多的時候 會有不少css標籤 transform (轉化, 瀏覽器環境下, 插入頁面以前) transform: './src/style/css.transform.js' // css.transform.js 文件內容 // 該函數並非在打包的時候執行的,在運行webpack的時候, 是不行執行的 // 在style-loader 將樣式插入到DOM中的時候 執行的, 運行的環境是在瀏覽器環境下, 能夠拿到瀏覽器參數, window,UA // 能夠根據當前瀏覽器來對當前的css進行形變 module.exports = function(css) { console.log(css) console.log(window.innerWidth) // 輸出形變之後的css if (window.innerWidth >= 768) { css = css.replace('yellow', 'dodgerblue') } else { css = css.replace('yellow', 'orange') } return css; } - 針對每一次在index.js中引入的css文件都會執行上面的代碼 CssLoader 配置參數 alias 解析的別名 將引入css的路徑映射到其餘地方 importLoader 取決於css處理後面是否是還有其餘的loader (sass會使用到 @import) minimize 是否壓縮 modules 是否啓用css-modules 打包出來的樣式class 都變成一段隨機字符串 CSS modules :local 給定一個本地的樣式 局部的樣式 :global 給定一個全局樣式 compose 繼承一個樣式 compose ... from path 引入一個樣式 (儘可能將composes放在前面, 這樣能夠控制引入順序, 樣式不會被覆蓋) // base.css .box { composes: big-box from './common.css'; height: 200px; width: 100px; border-radius: 4px; background: #696969; } localIdentName: '[[path]][name]_[local]--[hash:base64:5]' 控制生成的class類名 path表明引用css路徑 name表示文件名稱 local本地樣式名稱 配置less/sass npm install less-loader less --save-dev npm install sass-loader node-sass --save-dev .header { composes: font from './header.less' } 提取css代碼 - 提取公共代碼 作緩存 (不提取的話, 將css代碼打包到了js文件中) extract-loader ExtractTextWebpackPlugin npm install extract-text-webpack-plugin --save-dev // webpack3 var ExtractTextWebpackPlugin = require('ExtractTextWebpackPlugin) module: { rules: [ { test: /\.less$/, use: ExtractTextWebpackPlugin.extract({ fallback: { // 告訴webpack, 當不提取的時候, 使用何種方式將其加載到頁面中 loader: 'style-loader, options: { singleton: true, // transform: '' } }, use: [ {loader: 'css-loader'} {loader: 'less-loader'} ], // 定義咱們繼續處理的loader }) } ] }, plugins: [ new ExtractTextWebpackPlugin({ filename: '[name].min.css', // 提取出來的css的名稱 // 將css-loader的option中的minimize打開 // allChunks 給插件指定一個範圍, 指定提取css的範圍 // 1. 設置爲true 表示全部的引用的css文件都提取 // 2. 設置爲false, 默認, 只會提取初始化的css(異步加載不認爲是初始化) allChunks:false, }) ] // webpack3 結果: index.bundle.js app.min.css 可是打開index.html 並無插入進去 // webpack4 { test: /\.less$/, use: [ MiniCssExtractPlugin.loader, { loader: 'css-loader', // loader: 'file-loader' options: { minimize: process.env.NODE_ENV === 'production', modules: true, localIdentName: '[path]_[name]_[local]--[hash:base64:5]' } }, { loader: 'less-loader' } ] } plugins: [ new MiniCssExtractPlugin({ // Options similar to the same options in webpackOptions.output // both options are optional filename: '[name].css', chunkFilename: '[id].css' }) ] - 異步引入a.js文件, 在a.js文件中引入a.less 1. 針對allChunks爲false的狀況 - webpack3: 生成a.bundle.js文件, css文件被當成js的一個模塊被打包處理, 將css放在js文件裏面, 一塊兒被提取; css代碼切分的一種方式, 將初始化加載和動態加載區分開; 藉助動態加載的代碼區分, 也是css-in-js的一個概念 - weboack4: 生成moduleA.chunk.js 和moduleA.chunk.css文件, 在index.bundle.js 包括了對於modulA.js和module.css文件的引用 2. webpack4使用splitChunks配置 optimization: { splitChunks: { cacheGroups: { styles: { name: 'styles', test: /\.scss|css$/, chunks: 'all', // merge all the css chunk to one file enforce: true } } } } - 結果: 生成index.bundle.js style.chunk.js style.chunk.css 將全部的樣式文件都打包進了style.chunk.css文件中, 可是須要手動添加到項目htm中 - question: 爲何這裏不會運行? npm run extract PostCss (Autoprefixer CSS-nano CSS-next) A tool for transforming Css With Javascript用js去轉化css的一個工具 聯繫到上一節中的css.transform.js, 可是時機是不同的, PostCss是打包的時期, css.transform是瀏覽器插入到style標籤中的時候 postcss的強大, 理解成爲一個處理css的工具 安裝 npm install postcss postcss-loader autoprefixer cssnano postcss-cssnext --save-dev autoprefixer: 幫助加上瀏覽器前綴 css-nano 幫助咱們優化壓縮css, 在postcss能夠當作插件使用, css-loader就是用的css-nano作的壓縮 css-next 使用將來的css新語法 css variables custom selectors 自定義選擇器 calc() 動態計算 ... { loader: 'postcss-loader', options: { // require進來的插件給postcss使用的 ident: 'postcss', // 代表接下來的插件是給postcss使用的 plugins: [ // require('autoprefixer')(), // 兩個一塊兒用cssnext 會給出警告, 提示已經包含autoprefixer require('postcss-cssnext')() ] } }, 一旦涉及到瀏覽器兼容性問題的時候, 必定會有針對的瀏覽器兼容問題, 使用browserlist, 讓全部的插件都公用一份browserlist 能夠放在package.json裏面 .browserlistrc 存入對瀏覽器的要求 postcss-import 插件 將@import的文件內容直接放入到當前的css文件中, 可是存過來以後要考慮相對文件路徑的變化, 須要配合postcss-url來使用