前段時間我寫了個打包nodejs項目的文章,點擊前往
但是,問題很多。因為之前的項目是個歷史遺留項目,重構起來可能會爆炸,當時又比較急所以就寫個的適用范圍很小的webpack的打包方法。
最近稍微得空,便動了重構的心思,重構第一步當然要把架子搭起來
而搭架子的過程也是十分地艱辛啊,終於大概搞定了前端的部分,這一次就分享一下使用最新的webpack4怎么打包nodejs的多頁應用
歡迎大佬留言交流,想要源碼的點此前往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高編讀書筆記系列文章打個廣告
有什么問題,歡迎留言,也歡迎大佬指正,共同進步。
