1. 程式人生 > >webpack4打包nodejs專案進階版——多頁應用模板

webpack4打包nodejs專案進階版——多頁應用模板

前段時間我寫了個打包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了

 

 詳細程式碼在我的github上,幫到你們的小夥伴,跪求星星

 

基礎配置中還有一件事

那就是performance

webpack預設入口點檔案不能超過300k

超過後webpack會報warning

沒有強迫症的小夥伴可以跳過了

有兩個解決辦法:

1.關掉webpack的警告(一看就不能選)

2.設定performance

設定如下:

 

好了基本配置就完成了

接下來要針對,不同環境進行獨立的配置

 

開發環境配置

我先講開發環境的配置,生產環境的坑有點多放到最後講

對於開發環境來說,程式碼會經常修改而且,我們需要頻繁地檢視樣式,所以我們並不需要對檔案進行壓縮等處理

並且要讓它能夠熱更新即可,這裡我們使用webpack-server

配置程式碼如下:

這裡沒啥要注意的,直接按著配,run就行執行出來像下面這樣

頁面如下:

具體的我就不演示了

還是那句話github見

 

接下來開始重頭戲生產環境的配置

 

 

生產環境

為啥是重頭戲呢?生產環境那就是線上環境啊,效率、大小就是錢啊

另外呢,主要是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,原創不易,望支援

github連結

順便再給我的JS高編讀書筆記系列文章打個廣告

有什麼問題,歡迎留言,也歡迎大佬指正,共同進步。