webpack4打包nodejs專案進階版——多頁應用模板
前段時間我寫了個打包nodejs專案的文章,點選前往
但是,問題很多。因為之前的專案是個歷史遺留專案,重構起來可能會爆炸,當時又比較急所以就寫個的適用範圍很小的webpack的打包方法。
最近稍微得空,便動了重構的心思,重構第一步當然要把架子搭起來
而搭架子的過程也是十分地艱辛啊,終於大概搞定了前端的部分,這一次就分享一下使用最新的webpack4怎麼打包nodejs的多頁應用
歡迎大佬留言交流,想要原始碼的 ofollow,noindex"> 點此前往github
工程目錄
走個流程先上個專案結構圖
這裡先說明一下,為什麼除了webpack.config.js這個配置檔案之外還有一個config資料夾存放相關配置檔案
因為webpack分為了開發環境和生產環境,兩者在配置和表現形式上有所區別,放在一個檔案中不利於維護
這也算是一種解耦吧。
至於其他的一些檔案我這裡就大概提一下:
1.babelrc 配置babel-loader 用於將ES6+的JS程式碼轉為ES5的通用JS
2.eslint 主要用於程式碼的線上糾錯,以及一些語法錯誤的查詢
3.用於 git 的配置配置哪些檔案需要上傳到git
4. package.json就是用於設定專案資訊,以及專案的依賴
5.postcss 用於配置postcss 主要用於修復瀏覽器相容的問題
6, yarn 就是一個進階版的npm 可以並行下載 快取等(由facebook 研發)
以上就是整個架子的大概模板
接下來進入主題——webpack的相關配置
cross-env跨平臺設定環境變數
通過cross-env 來判斷當前的環境(即生產環境、開發環境)
用法如下:
在package.json中設定啟動命令
將 NODE_ENV 設定為不同的值
根據該值來判斷當前的環境
Webpack.config.js
通常來說該檔案就是webpack 的核心配置檔案
但為降低不同環境的耦合度,使程式碼邏輯更加清晰
我使用這個檔案作為一個“路由” 根據之前的 NODE_ENV 去請求不同的webpack配置檔案
程式碼如下:
為了相容VUE等框架所以我的ESlint 設為不以分號結尾
config資料夾
我所有的webpack配置資料夾都存放在該資料夾下
上方要獲取的配置檔案都在這裡
我的想法是在 base.js 中存放兩種環境的公共程式碼
dev.js、prod.js存放對應環境的特殊配置程式碼
最後輸出的檔案只能有一個webpack的配置檔案
所以使用
webpack-merge
來合併兩個webpack配置檔案
webpack基礎配置
下面我們來一 一分析每個配置檔案
首先就是base.js
程式碼如下:
/** * webpack 基礎配置 */ const webpack = require('webpack') const path = require('path') const fs = require('fs') const Entries = {} // 儲存檔案入口 const pages = []// 存放html-webpack-plugin例項 const env = process.env.NODE_ENV !== 'prod' // 判斷執行環境 const MiniCssExtractPlugin = require('mini-css-extract-plugin') // 引入mini-css-extract-plugin const HtmlWebpackPlugin = require('html-webpack-plugin'); //獲取html-webpack-plugin例項集合 (function () { let pagePath = path.join(__dirname, '../src/page')// 定義存放html頁面的資料夾路徑 let paths = fs.readdirSync(pagePath) // 獲取pagePath路徑下的所有檔案 paths.forEach(page => { page = page.split('.')[0]// 獲取檔名(不帶字尾) pages.push(new HtmlWebpackPlugin({ filename: `views/${page}.html`, // 生成的html檔案的路徑(基於出口配置裡的path) template: path.resolve(__dirname, `../src/page/${page}.html`), // 參考的html模板檔案 chunks: [page, '[name]', 'commons', 'vendors', 'manifest'], // 配置生成的html引入的公共程式碼塊 引入順序從右至左 favicon: path.resolve(__dirname, '../src/img/favicon.ico'), // 配置每個html頁面的favicon minify: {// 配置生成的html檔案的壓縮配置 collapseWhitespace: true, collapseInlineTagWhitespace: true, conservativeCollapse: true, minifyCSS: true, minifyJS: true, removeComments: true, trimCustomFragments: true } })) Entries[page] = path.resolve(__dirname, `../src/js/${page}.js`)// 入口js檔案 }) })() module.exports = { // 配置入口檔案 entry: Entries, // 啟用 sourceMap devtool: 'cheap-module-source-map', // mode為none表示這是預設配置 mode: 'none', // 配置檔案出口 output: { // 將打包好的js輸出到public(靜態資源目錄)下的js資料夾中 filename: 'public/js/[name].bundle.[hash].js', path: path.resolve(__dirname, '../dist'), // 輸出目錄,所有檔案的輸出路徑都基於此路徑之上(需要絕對路徑) publicPath: '../' }, // 省略檔案字尾 resolve: { extensions: ['.js'] // 配置過後,書寫該類檔案路徑的時候可以省略檔案字尾 }, // loader module: { rules: [ // 使用expose處理JQuery(JQ使用npm安裝)配置了這一條後就不要使用external(主要用於cdn引入) { test: require.resolve('jquery'), // 此loader配置項的目標是NPM中的jquery loader: 'expose-loader?$!expose-loader?jQuery' // 先把jQuery物件宣告成為全域性變數`jQuery`,再通過管道進一步又宣告成為全域性變數`$` }, // 處理html中的圖片,考慮到node使用模板的情況所以不能使用html-loader { test: /\.html$/, use: [{ loader: 'html-withimg-loader' // 處理img標籤中的圖片 }] }, // 處理樣式表 { test: /\.(sa|sc|c)ss$/, use: [ env ? 'style-loader' : MiniCssExtractPlugin.loader, 'css-loader', 'postcss-loader', 'sass-loader' ] }, { test: /\.(less)$/, use: [ env ? 'style-loader' : MiniCssExtractPlugin.loader, 'css-loader', 'postcss-loader', 'less-loader' ] }, // 使用babel處理js檔案 { test: /\.m?js$/, exclude: /(node_modules|bower_components)/, use: 'babel-loader' }, // 處理圖片 { test: /\.(png|jpg|gif|svg)$/, use: [{ loader: 'url-loader', options: { limit: 10000, // 設定影象大小超過多少轉存為單獨圖片 name: 'public/img/[name].[hash].[ext]' // 轉存的圖片目錄 } }] }, // 處理字型 { test: /\.(woff|woff2|eot|ttf|otf)$/, use: ['url-loader'] } ] }, // 配置外掛 plugins: [ // 分離tml-webpack-plugin例項陣列、引入jq ...pages, new webpack.ProvidePlugin({ $: 'jquery', jQuery: 'jquery', 'window.$': 'jquery', 'window.jQuery': 'jquery' }) ], // 配置webpack執行相關 performance: { maxEntrypointSize: 1000000, // 最大入口檔案大小1M maxAssetSize: 1000000 // 最大資原始檔大小1M } }
關於上述程式碼
首先我們要配置的是入口:
我這裡使用一個函式來遍歷page資料夾中的所有html檔案
這裡我們約定對應html的js與html同名以便我們自動化生成入口物件
如下圖所示:
這樣我們就能使用html的名字來設定入口了,獲取的入口物件如下:
該函式的另一個功能就是,根據html檔案使用
html-webpack-plugin
來自動生成我們的html頁面
看到這裡或許有的小夥伴會有疑問,為啥不用html-loader來解析html檔案然後打包進去?
正好我也解答一下一些,node專案中使用ejs等模板的小夥伴的疑問,不是html怎麼辦?
原因如下:
1.我這個架子主要考慮的是node專案,通常來說node不管是做中間層,還是做全棧都有可能會使用模板引擎
而html-loader無法解析ejs等模板語法
2.以ejs來舉例,如果我使用ejs-loader來解析呢?如果使用ejs-loader那麼只能適用於用ejs做元件化開發的
情況,而不能適用於使用ejs做資料渲染(中間層)的情況
3.那麼在已經是ejs等模板的情況下的node專案怎麼使用我的架子呢?
答案很簡單,在app.js中加入以下程式碼(express),若還是不懂參考我 上一篇初級版的webpack
4 從另一個方面來說將ejs等檔案改為html檔案有利於搜尋引擎優化(小聲嗶嗶)
關於入口和html的問題就解答到這
下一步我們就應該配置出口了
話不多說先上程式碼:
如果是搞node的小夥伴應該知道public(靜態資源目錄)
所以我將webpack打包後的檔案輸出到該目錄下
關於publicpath 我這裡用的相對路徑,就是讓webpack-server 的專案根路徑和我的靜態資原始檔一致 不然 run dev 的時候會404
在這裡提一下JQuery的問題,目前來說jq有三種引入方式
1.cdn 引入
2.import 本地檔案
3.expose-loader 暴露出 npm 安裝的jquery
這裡我採用的是第三種方法
有幾個好處
1.在頁面中不用顯式地引入jq了 (懶是人類進步的第一生產力)
2.使jq也納入了npm模組化管理的範疇
3. 前面兩點足夠了,emm
程式碼如下
說完了jq的問題然後就是配置不同檔案的loader了
基礎配置中還有一件事
那就是performance
webpack預設入口點檔案不能超過300k
超過後webpack會報warning
沒有強迫症的小夥伴可以跳過了
有兩個解決辦法:
1.關掉webpack的警告(一看就不能選)
2.設定performance
設定如下:
好了基本配置就完成了
接下來要針對,不同環境進行獨立的配置
開發環境配置
我先講開發環境的配置,生產環境的坑有點多放到最後講
對於開發環境來說,程式碼會經常修改而且,我們需要頻繁地檢視樣式,所以我們並不需要對檔案進行壓縮等處理
並且要讓它能夠熱更新即可,這裡我們使用webpack-server
配置程式碼如下:
這裡沒啥要注意的,直接按著配,run就行執行出來像下面這樣
頁面如下:
具體的我就不演示了
接下來開始重頭戲生產環境的配置
生產環境
為啥是重頭戲呢?生產環境那就是線上環境啊,效率、大小就是錢啊
另外呢,主要是webpack4 和 min-css的配合有點問題,我這搭架子的時候搞的我頭皮發麻
我不太清楚這是bug還是我的操作有啥問題
好了,進入正題
關於生產環境,主要的配置是:
1.要能夠刪除之前的過期檔案,手動刪多low啊
2.要壓縮程式碼,用webpack的目的是啥,除了構建自動化的前端工作流之外,最主要的目的無非是壓縮程式碼嘛
壓縮程式碼的好處我這裡就不說了,網上一搜一堆
好了開搞
首先清理過期程式碼:
這一步就完成了
下一步抽離css樣式
這裡要說一下,webpack4中抽離css要使用
mini-css-extract-plugin
原來的那個在webpack4不能使用
這裡我要吐槽一下官網給的示例,坑了我一下
這裡的兩個屬性是類似域output中的同名屬性的,一般來說只用配置一個就行
另外可能就是這個外掛有點bug
我先說一下我希望達到的效果
我希望將每個html的所有css作為一個單獨檔案
最好再將css的重複程式碼提取一下
如果不將css提取成一個單獨的檔案就沒法CDN加速了啊
但是問題來了沒法提取公共css程式碼,網上有的說用Extractcss那個外掛的@next可以搞,我試了一下只能不重複打包,不能提取公共程式碼
我覺得人家既然專門為webpack4新出了一個,應該是有過人之處的,所以我就沒有用這個方法
我就自己開始折騰,我試著用那個提取js重複程式碼的
splitChunks
我試了一下竟然可以處理css,但是有個問題,生成的公共CSS沒法自動引入html頁面
因為splitChunks是處理js的沒法自動引入css
如果實在有提取公共css需求的小夥伴,頁面又不多的情況(指你願意手動引入)
不妨試試這種方法
主要步驟如下
在spplitChunks中建立快取組過濾掉所有的js檔案
然後再建一個優先順序很低的快取組,將剩下的檔案中字尾為css的檔案都強制提取到該組
用enforce:true 就可以提取出來,由於不是本文主題,也不知道是不是個bug,感興趣的小夥伴可以留言我私聊,這裡就不過多去講了
繼續來說,我這提不提取公共css影響不大
所以我的程式碼如下:
/** * 生產環境配置 */ const webpackBase = require('./webpack.config.base') // 引入基礎配置 const path = require('path') const MiniCssExtractPlugin = require('mini-css-extract-plugin') //提取css const webpackMerge = require('webpack-merge') // 引入 webpack-merge 外掛 const CleanWebpackPlugin = require('clean-webpack-plugin') // 清理dist資料夾 // 合併配置檔案 module.exports = webpackMerge(webpackBase, { plugins: [ new MiniCssExtractPlugin({// 提取出的Css的相關配置 filename: 'public/css/[name].[hash].css' // 檔案存放路徑 }), new CleanWebpackPlugin(['dist'], {// 自動清理 dist 資料夾 root: path.resolve(__dirname, '../'), // 根目錄 verbose: true, // 開啟在控制檯輸出資訊 dry: false // 啟用刪除檔案 }) ], optimization: { minimize: true, splitChunks: {// 配置提取公共程式碼 chunks: 'all', minSize: 30000, // 配置提取塊的最小大小(即不同頁面之間公用程式碼的大小) minChunks: 3, // 最小共享塊數,即公共程式碼最少的重複次數一般設為3 automaticNameDelimiter: '.', // 生成的名稱指定要使用的分隔符 cacheGroups: {// 設定快取組 vendors: { name: 'vendors', test (module) { let path = module.resource return /[\\/]node_modules[\\/]/.test(path) || /[\\/]lib[\\/]/.test(path) }, priority: 30 }, commons: { name: 'commons', test: /\.js$/, enforce: true, priority: 20 } } }, runtimeChunk: { name: 'manifest' // 打包執行檔案 } } })
這裡我為js設定了兩個快取組,並提取出了執行時的manifest
一個是依賴的外掛等js(滿足3個頁面引用)生成 vender.js
不滿足3個或自己寫的js提取到commons.js中
結語
以上就是webpack4 打包 nodejs 專案的架子,如果幫到你的小夥伴可以,關注我、收藏走一波
需要程式碼的小夥伴請移步github,原創不易,望支援
順便再給我的 JS高編讀書筆記系列文章 打個廣告
有什麼問題,歡迎留言,也歡迎大佬指正,共同進步。