webpack4.0 CheatSheet
還在為webpack的配置而煩惱嗎?這裡有一份webpack從簡易到高階版本的配置。還附贈配置地址 ,你想要嗎?不,你不想。老老實實自己配置去吧。
壓箱底的筆記而已,大家看看樂樂就好了,這是筆者為了練習webpack而嘗試了不同的配置方式,參考了create-react-app的webpack配置。以及學習瞭如何自己寫一個簡易的proxy。
Loaders全配置
Loader | 作用 |
---|---|
html-loader | --- |
html-webpack-plugin | --- |
style-loader | --- |
mini-css-extract-plugin | 劃重點,webpack4.0之後不再使用extract-text-webpack-plugin |
css-loader | 一個將CSS變成JS的loader,筆者認為它的modules模組化是一個很實用的功能,大愛 |
sass-loader | 一個SASS的處理器,先將scss編譯成css,然後css再做進一步的處理 |
node-sass | 編譯scss依賴的包 |
postcss-loader | 一款配合autoprefix,autoprefixer.github.io/ 自動給CSS加惱人的字首 |
ts-loader | 如果不用babel編譯ts,則需要ts-loader |
file-loader | 匯入檔案,比如json,變成js的格式 |
url-loader | 類似於file-loader,不過比file-loader智慧,在檔案過大的情況下可以只加載一個地址,而不用將檔案載入 |
babel-loader | 別說了,es6就需要他編譯 |
webpack | 大家都懂的,核心 |
webpack-cli | 有了啟動編譯變得簡單 |
babel配置
現在二進位制可以直接編譯js
npx babel src --out-dir lib 複製程式碼
大聲告訴我Babel是幹什麼的? ——因為JS語法一直在修訂進步,而使用者使用的瀏覽器更新頻率不如JS語法更新的快,因此需要一個編譯JS語法,使相容支援不同時期JS語法的瀏覽器。
npm install --save-dev @babel/core @babel/cli @babel/preset-env npm install --save @babel/polyfill npm install --save-dev bable-loader 複製程式碼
@babel/polyfill | vs | @babel/preset-env |
---|---|---|
無視語法,直接require缺失api | 救得了各種新穎寫法(如箭頭函式),救不了api | |
核心包core/js |
@babel/preset-env
babel-preset-es2015 babel-preset-es2016 babel-preset-es2017 babel-preset-latest A combination of the above ^ .... 複製程式碼
噩夢般的配置,剛學習babel的時候,看到這些配置,隱約覺得自己學不會了。但是現在babel-preset-env
就可以搞定所有了!。
@babel/plugin-proposal-class-propertiesbabeljs.io/docs/en/bab…
@babel/polyfill
這個包已經被babel給移除了,它僅僅是core-js的別名。
其他plugin
REACT全配置
多安裝一個babel即可
npm install --save-dev @babel/preset-react npm install --save-dev react react-dom 複製程式碼
TS全配置
npm install --save-dev typescript 複製程式碼
ts可以用babel編譯
npm install --save-dev @babel/preset-typescript 複製程式碼
ts也可以用ts-loader編譯,需要配置tsconfig.js
npm install --save-dev ts-loader 複製程式碼
有關CSS載入的一些見解
CSS loader可以很簡單,也可以相當複雜,一般的需求有以下幾點:
- 有效CSS,直接inline的載入
- 希望可以單獨生成一個檔案,然後url載入
- 希望可以壓縮一下
- 想要自動加字首的功能
- 使用SCSS等,高階CSS處理器
參考create-react-app的配置檔案,寫的一個一本滿足的css loader大餐:
// mini-css-extract-plugin,有了他可以代替style-loader,不僅壓縮了檔案,還可以幫助我們將CSS從js中剝離出來 const MiniCssExtractPlugin = require("mini-css-extract-plugin"); //loaders的主體,按照loader的順序應該是先是預處理,比如Scss,然後是加字首prefix,到此為止大家還是CSS的樣子,等到了css-loader,css就變成了js,最後style-loader或者minicss將css模組再變成js生成檔案或者內聯。 const getStyleLoaders = (cssOptions, preProcessor,env) => { const loaders = [ // 此處是最後一步,將CSS提取出或者內聯 {loader:env==="development"?require.resolve('style-loader') : MiniCssExtractPlugin.loader}, // 此處將CSS變成標準的JS模組,如果有css模組化的需求,是在此處理 { loader: require.resolve('css-loader'), options: cssOptions, }, // 此處將CSS預處理處,一般是給css加上惱人的字首,這裡可以設定瀏覽器的版本,你需要哪些瀏覽器的支援 { loader: require.resolve('postcss-loader'), options: { ident: 'postcss', plugins: () => [ require('postcss-flexbugs-fixes'), require('postcss-preset-env')({ autoprefixer: { flexbox: 'no-2009', }, stage: 3, }), ], }, }, ]; // 此處如果有sass等預處理的需求,需要在此配置 if (preProcessor) { loaders.push(require.resolve(preProcessor)); } return loaders; }; // style files regexes,這裡是為了避免重複配置css的各種型別 const cssRegex = /\.css$/; const cssModuleRegex = /\.module\.css$/; const sassRegex = /\.(scss|sass)$/; const sassModuleRegex = /\.module\.(scss|sass)$/; // 匯出給webpack配置使用 module.exports={getStyleLoaders,cssRegex,cssModuleRegex,sassRegex,sassModuleRegex} 複製程式碼
HMR熱更新配置——減少手動編譯重新整理頁面
webpack自帶的伺服器
npm install --save-dev webpack-dev-server 複製程式碼
devServer: { contentBase: path.join(__dirname, 'dist1'), compress: true, hot: true, port: 9000 }, entry: [ require.resolve('webpack-dev-server/client') + '?/', require.resolve('webpack/hot/dev-server'), path.resolve(__dirname,'src/index') ], plugins: [ new webpack.HotModuleReplacementPlugin() ] 複製程式碼
webpack自帶的伺服器的跨域問題
devServer: { proxy: { '/slider': { target: 'http://www.cherryvenus.com/', secure: false, changeOrigin: true//important 能解決大多數404無法訪問的問題 } } }, 複製程式碼
自定義伺服器
npm install --save-dev express webpack-hot-middleware webpack-dev-middleware 複製程式碼
client,webpack,此處不能忘,其實就是一個連結websocket的一個類似於外掛的程式,記得production的時候將此處移除,不然bundle會很大呢。
entry: [ require.resolve('webpack-hot-middleware/client') + '?path=/__what&timeout=2000&overlay=false', path.resolve(__dirname,'src/index') ], plugins: [ new webpack.HotModuleReplacementPlugin() ] 複製程式碼
server,此處的path配置需要和客戶端的path一致。
app.use(require("webpack-hot-middleware")(compiler,{ log: false, path: "/__what", heartbeat: 2000 })); 複製程式碼
entry入口的client新增模組,不accept,程式無法自動重新整理。
if(module.hot){ module.hot.accept() } 複製程式碼
http-proxy-middleware——解決開發中的跨域問題
npm install --save-dev http-proxy-middleware 複製程式碼
var proxyMiddleWare = require("http-proxy-middleware"); var proxyPath = "http://www.cherryvenus.com/"; var proxyOption ={target:proxyPath,changeOrigoin:true}; app.use(proxyMiddleWare("/slider",proxyOption)) 複製程式碼
壓箱底的proxy研究
基於第三方外掛的proxy配置開發
無論是http-proxy-middleware還是webpack-dev-server的proxy都是基於http-proxy-middleware,而http-proxy-middleware是基於node-http-proxy。
參考網址:
var proxyPath = "http://www.cherryvenus.com/"; var proxyOption ={target:proxyPath,changeOrigin:true,selfHandleResponse : true,ignorePath:true}; const proxy = httpProxy.createProxyServer({}) //如果selfHandleResponse為true就是可以自己修改獲得的內容,如果不需要修改就是獲取轉發就無需設定了。 // 如果想要自定義獲取到的內容,則可以通過觸發這個事件`proxyRes`來截獲內容並修改 proxy.on('proxyRes', function (proxyRes, req, res) { var body = new Buffer(''); // 這裡筆者複製了header資訊,省去了一波配置 for(let i in proxyRes.headers){ res.setHeader(i, proxyRes.headers[i]); } proxyRes.on('data', function (data) { body = Buffer.concat([body, data]); }); proxyRes.on('end', function () { body = Buffer.from(body); res.write(body); res.end() res.rs() }); proxyRes.on('error', function (err, req, res) { res.writeHead(500, { 'Content-Type': 'text/plain' }); res.end('Something went wrong. And we are reporting a custom error message.'); }); }) // 此處僅僅是配置了一個連結而已,真正觸發代理,是下方的proxy.web(req, res, proxyOption); app.use("/slider/",(req, res, next)=>{ // 當地址地位到 let p= new Promise((rs,rj)=>{ // 先關閉之前的代理,如果有 proxy.close(); res.rs=rs // 請求一個新的連結 proxy.web(req, res, proxyOption); }) p.then(()=>{ next() }) return p }) 複製程式碼
手動寫一個
看到上面的解決方法,突然靈光一閃,那麼我是不是可以自己寫一個呢?這個流程並不複雜,其實代理就是抓取網頁然後轉發的過程,既然如此,寫一個簡易的proxy並不是什麼難事。
const http=require("http") // 建立伺服器 http.createServer({ host : 'localhost'}, (req, res) => { // 開啟一個請求 http.get('http://www.baidu.com', (res1) => { // 抓取內容 var body = new Buffer(''); res1.on('data', (chunk) => { body = Buffer.concat([body, chunk]); }); res1.on('end', () => { body = Buffer.from(body); // 複製頭部資訊 for(let i in res1.headers){ res.setHeader(i, res1.headers[i]); } // 返回內容,代理成功 res.write(body); res.end(); }); }) }).listen(8000); 複製程式碼
代理就是如此簡單,當然這只是一個原理,一種實現方法,就是抓取網頁再列印到頁面上。
然後筆者在查閱node中http發現一個驚為天人的寫法(也許是筆者見識淺薄),一個利用了Http的method為CONNECT的方法,將當前連結處於連結狀態,也就是不會斷,然後用 net.connect這個方法,連結到了請求連結,建立了互讀互寫的管道。
const http=require("http") const net=require("net") const url=require("url") const proxy = http.createServer((req, res) => { const options = { port: 1337, host: 'localhost', method: 'CONNECT',//會觸發"connect" https://nodejs.org/api/http.html#http_event_connect path: 'www.baidu.com:80' }; const req1 = http.request(options); req1.end(); req1.on('connect', (res1, socket, head) => { let context=new Buffer("") // make a request over an HTTP tunnel socket.write('GET / HTTP/1.1\r\n' + 'Host: www.baidu.com:80\r\n' + 'Connection: close\r\n' + '\r\n'); socket.on('data', (chunk) => { context = Buffer.concat([context, chunk]); }); socket.on('end', () => { for(let i in req.headers){ res.setHeader(i, req.headers[i]); } context=context.toString().split("\n\r\n") context.shift() res.end(context.join("")) }); }); }).listen(1337); proxy.on('connect', (req, cltSocket, head) => { const srvUrl = url.parse(`http://${req.url}`); const srvSocket = net.connect(srvUrl.port, srvUrl.hostname) cltSocket.write('HTTP/1.1 200 Connection Established\r\n' + 'Proxy-agent: Node.js-Proxy\r\n' + '\r\n'); srvSocket.write(head); //管道建立了互讀互寫 srvSocket.pipe(cltSocket).pipe(srvSocket) }); 複製程式碼
這個方法的簡易原理是這樣的,當你訪問http server的時候,http server調起了一個訪問自身連結,並且設定method為CONNECT,也就是不間斷的意思,並建立了一個clientSocket,然後此http server也監聽了connect的事件,當有method為CONNECT的連結的時候,會觸發http server的connect。此時連結打通之後,http server就會建立一個net的連結,去訪問需要代理的網站,返回一個serverSocket,然後serverSocket負責讀取內容,然後寫入clientSocket,clientSocket再告訴serverSocket寫入成功,直至代理成功。
webpack的拆包實踐(三種方法)
optimization.splitChunks
真拆包,參考連結
module.exports = { //... optimization: { splitChunks: { // ... minChunks: 2,// 這個屬性配置了當前模組在不同的模組中出現的次數,如果出現了引用兩次的情況,則複用打包出來,這個是真拆包,拆的自己包。 // ... } } }; 複製程式碼
externals剔除不必要的依賴包
只要注意root的問題,root在web下相當於window,因此是window.React,prop-type更不用說了。
externals:{ react:{ root: 'React', amd: 'react', commonjs: 'react', commonjs2: 'react' }, "prop-types":{ root: 'PropTypes', amd: 'prop-types', commonjs: 'prop-types', commonjs2: 'prop-types' } } 複製程式碼
ddl和external的區別
exteranl是剔除別人的包。
ddl是建立自己的工具庫。
const library = '[name]_lib' module.exports = { mode:"production", entry: { vendors: ['react', 'react-dom'] }, output: { filename: '[name].dll.js', path: path.join(__dirname,"dist/vendor"), //libraryTarget: "umd", library }, plugins: [ new webpack.DefinePlugin({ 'process.env': { //用於打包react中的東西 NODE_ENV: JSON.stringify("production") } }), new webpack.DllPlugin({ path: path.join(__dirname, 'dist/[name]-manifest.json'), // This must match the output.library option above name: library }), ] } 複製程式碼
production中使用動態連結庫,此處不要配置external。
new webpack.DllReferencePlugin({ context: __dirname, manifest: path.join(__dirname, 'dist/vendors-manifest.json'), }) 複製程式碼