從零學腳手架(六)---production和development拆分

莫問今朝·發表於2021-03-15

development、production拆分

根據檔案拆分

webpack打包時分為開發模式(development)釋出模式(production),在前面使用命令引數做了簡單區分。

從零學腳手架(六)---production和development拆分

但這種方式區分在做定製化模式時有些不太方便,所以需要對兩種模式做徹底拆分。

之前介紹過,webpack可以使用 --config 引數指定配置檔案,所以可以不同模式使用不同配置檔案。

從零學腳手架(六)---production和development拆分 從零學腳手架(六)---production和development拆分

? 將webpack配置檔案放入一個指定的目錄,方便管理,

/buiild/config.js 是控制路徑的物件,在此進行管理, config.js 檔案位於 /build 下,所以所以 root 屬性則改為指向上級目錄

const path = require('path')

module.exports.config = {
  root: path.join(__dirname, '../'),
}

此時可以在兩個配置檔案中設定做定製化配置項

webpack.dev.js

const path = require('path')
const webpack = require("webpack");
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
//	引用config物件,因匯出時為 module.exports.config 所以在此使用 { config }匯入
const { config }  = require('./config');

//	browserslist環境變數
process.env.BROWSERSLIST_ENV = 'development'

const modules = {
    mode:'development',

    entry: path.join(config.root, 'src/index.js') ,

    output: {
        path: path.join(config.root, 'dist') ,
        //	dev模式下不需要快取
        filename: '[name].js'
    },

    module:{
        rules:[
            {
                //  所有的.js(x?)檔案都走babel-loader
                test:/\.js(x?)$/,
                include: path.join(config.root, 'src'),
                loader: "babel-loader"
            }
        ]
    },

    //  dev不需要壓縮
    optimization: {
        minimize: false,
    },

    plugins: [
        new HtmlWebpackPlugin({
            //  HTML的標題,
            //  template的title優先順序大於當前資料
            title: 'my-cli',

            //  輸出的html檔名稱
            filename: 'index.html',

            //  本地HTML模板檔案地址
            template: path.join(config.root, 'src/index.html'),

            // 引用JS檔案的目錄路徑
            publicPath: './',

            //  引用JS檔案的位置
            //  true或者body將打包後的js指令碼放入body元素下,head則將指令碼放到中
            //  預設為true
            inject: 'body',

            //  載入js方式,值為defer/blocking
            //  預設為blocking, 如果設定了defer,則在js引用標籤上加上此屬性,進行非同步載入
            scriptLoading: 'blocking',

            //  是否進行快取,預設為true,在開發環境可以設定成false
            cache: false,

            //  新增mate屬性
            meta: {}
        }),

        new CleanWebpackPlugin({
            // 是否假裝刪除檔案
            //  如果為false則代表真實刪除,如果為true,則代表不刪除
            dry: false,

            //  是否將刪除日誌列印到控制檯 預設為false
            verbose: true,

            //  允許保留本次打包的檔案
            //  true為允許,false為不允許,保留本次打包結果,也就是會刪除本次打包的檔案
            //  預設為true
            protectWebpackAssets: true,

            //  每次打包之前刪除匹配的檔案
            cleanOnceBeforeBuildPatterns: ['**/*'],

            //  每次打包之後刪除匹配的檔案
            cleanAfterEveryBuildPatterns:["*.js"],
        }),

        new webpack.DefinePlugin({ "global_a": JSON.stringify("我是一個打包配置的全域性變數") }),
    ],

    resolve: {
        alias:{
            //  設定路徑別名
            '@': path.join(config.root, 'src'),

            '~': path.join(config.root, 'src/assets'),
        },

        //  可互忽略的字尾
        extensions:['.jsx', '.js', '.json'],

        //  預設讀取的檔名
        mainFiles:['index', 'main'],
    }
}

//  使用node.js的匯出,將配置進行匯出
module.exports = modules

webpack.pro.js

const path = require('path')
const webpack = require("webpack");
const HtmlWebpackPlugin = require('html-webpack-plugin')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const TerserPlugin = require('terser-webpack-plugin')
//	引用config物件,因匯出時為 module.exports.config 所以在此使用 { config }匯入
const { config }  = require('./config');

//	browserslist環境變數
process.env.BROWSERSLIST_ENV = 'production'

const modules = {

  mode: 'production',

  entry: path.join(config.root, 'src/index.js'),

  // prod需要快取js
  output: {
    path: path.join(config.root, 'dist') ,
    filename: '[name]_[contenthash].js'
  },

  module:{
    rules:[
      {
        //  所有的.js(x?)檔案都走babel-loader
        test:/\.js(x?)$/,
        include: path.join(config.root, 'src') ,
        loader: "babel-loader"
      }
    ]
  },


  optimization: {
    minimize: true,
      minimizer: [
       new TerserPlugin({
          //  指定壓縮的檔案
          include: /\.js(\?.*)?$/i,

          // 排除壓縮的檔案
          // exclude:/\.js(\?.*)?$/i,

          //  是否啟用多執行緒執行,預設為true,開啟,預設併發數量為os.cpus()-1
          //  可以設定為false(不使用多執行緒)或者數值(併發數量)
          parallel: true,

          //  可以設定一個function,使用其它壓縮外掛覆蓋預設的壓縮外掛,預設為undefined,
          minify: undefined,

          //  是否將程式碼註釋提取到一個單獨的檔案。
          //  屬性值:Boolean | String | RegExp | Function<(node, comment) -> Boolean|Object> | Object
          //  預設為true, 只提取/^\**!|@preserve|@license|@cc_on/i註釋
          //  感覺沒什麼特殊情況直接設定為false即可
          extractComments: false,

          // 壓縮時的選項設定
          terserOptions: {
            //  是否保留原始函式名稱,true代表保留,false即保留
            //  此屬性對使用Function.prototype.name
            //  預設為false
            keep_fnames: false,

            // 是否保留原始類名稱
            keep_classnames: false,

            //  format和output是同一個屬性值,,名稱不一致,output不建議使用了,被放棄
            // 指定壓縮格式。例如是否保留*註釋*,是否始終為*if*、*for*等設定大括號。
            format: {
              comments: false,
            },
            output: undefined,

            //  是否支援IE8,預設不支援
            ie8: false,

            compress: {
              // 是否使用預設配置項,這個屬性當只啟用指定某些選項時可以設定為false
              defaults: false,

              // 是否移除無法訪問的程式碼
              dead_code: false,

              // 是否優化只使用一次的變數
              collapse_vars: true,

              warnings: true,

              //  是否刪除所有 console.*語句,預設為false,這個可以線上上設定為true
              drop_console: false,

              //  是否刪除所有debugger語句,預設為true
              drop_debugger: true,

              //  移除指定func,這個屬性假定函式沒有任何副作用,可以使用此屬性移除所有指定func
              // pure_funcs: ['console.log'], //移除console
            },
          },
        })
    ]
  },

  plugins: [
   new HtmlWebpackPlugin({
      //  template的title優先順序大於當前資料
      title:'my-cli',
      //  檔名稱
      //  模板路徑
      template:path.join(__dirname, 'src/index.html'),
      // 用於打包後引用指令碼時的路徑
      publicPath:'./',

      //  是否將打包的資源引用到當前HTML, false代表不引用
      //  true或者body將打包後的js指令碼放入body元素下,head則將指令碼放到中
      //  預設為true
      inject:'body',
      //  載入js方式,值為defer/blocking
      //  預設為blocking, 如果設定了defer,則在js引用標籤上加上此屬性,進行非同步載入
      scriptLoading:'blocking',

      //  是否進行快取,預設為true,在開發環境可以設定成false
      cache:false,
      //  新增mate屬性
      meta:{}
    }),

    new CleanWebpackPlugin({
      dry:false,
      //  是否列印日誌到控制檯 預設為false
      verbose: true,
      //  允許保留本次打包的檔案
      //  true為允許,false為不允許,保留本次打包結果,也就是會刪除本次打包的檔案
      //  預設為true
      protectWebpackAssets:true,
      //  每次打包之前刪除匹配的檔案
      cleanOnceBeforeBuildPatterns:[],

      //  每次打包之後刪除匹配的檔案
      cleanAfterEveryBuildPatterns:["*.js"],
    }),

    new webpack.DefinePlugin({ "global_a": JSON.stringify("我是一個打包配置的全域性變數") }),
  ],

  resolve: {
    alias:{
      //  設定路徑別名
      '@': path.join(config.root, 'src'),

      '~': path.join(config.root, 'src/assets')
    },
    //  可互忽略的字尾
    extensions:['.jsx', '.js', '.json'],
    //  預設讀取的檔名
    mainFiles:['index', 'main'],
  }
}

//  使用node.js的匯出,將配置進行匯出
module.exports = modules

webpack-merge

根據檔案拆分之後,發現兩個檔案中具有好多共同的配置資訊。這樣寫兩份反而不太方便管理了。

此時就需要一種能提供出一個 公共配置檔案模組(common) 和兩個定製化配置檔案。在定製化配置檔案中匯入 公共配置檔案模組(common) 並進行合併配置

webpack社群中提供了一個webpack-merge庫,就是允許我們對webpack配置屬性合併。

yarn add -D webpack-merge@5.7.3

抽出一個 webpack.common.js 檔案,將公共配置配置在此

從零學腳手架(六)---production和development拆分
const path = require('path')
const webpack = require("webpack");
const HtmlWebpackPlugin = require('html-webpack-plugin')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const TerserPlugin = require('terser-webpack-plugin')
//	引用config物件,因匯出時為 module.exports.config 所以在此使用 { config }匯入
const { config }  = require('./config');

//  isDev  boolean

//  使用node。js的匯出,將配置進行匯出
module.exports =  (isDev = true) => {

  //  根據引數設定browserslist環境變數
  process.env.BROWSERSLIST_ENV = isDev ? 'development' : 'production'

  return {
    //  入口檔案
    //  字串形式
    entry: path.join(config.root, 'src/index.js'),
      
    // 根據isDev引數判斷是否快取JS
    // 輸出檔案
    output: {
      //   輸出檔案的目錄地址
      path: path.join(config.root, 'dist'),
      //  輸出檔名稱,contenthash代表一種快取,只有檔案更改才會更新hash值,重新打包
      filename: `[name]${isDev ? '' : '_[contenthash]'}.js`,
    },

    //devtool:false, //'eval'

    module: {
      rules: [
        {
          //  所有的.js(x?)檔案都走babel-loader
          test: /\.js(x?)$/,
          include: path.join(config.root, 'src'),
          loader: "babel-loader"
        },
      ]
    },

    optimization: {
      // 根據當前模式進行判斷是否需要壓縮
      minimize: !isDev,
        
      minimizer: [
         new TerserPlugin({
          //  指定壓縮的檔案
          include: /\.js(\?.*)?$/i,

          // 排除壓縮的檔案
          // exclude:/\.js(\?.*)?$/i,

          //  是否啟用多執行緒執行,預設為true,開啟,預設併發數量為os.cpus()-1
          //  可以設定為false(不使用多執行緒)或者數值(併發數量)
          parallel: true,

          //  可以設定一個function,使用其它壓縮外掛覆蓋預設的壓縮外掛,預設為undefined,
          minify: undefined,

          //  是否將程式碼註釋提取到一個單獨的檔案。
          //  屬性值:Boolean | String | RegExp | Function<(node, comment) -> Boolean|Object> | Object
          //  預設為true, 只提取/^\**!|@preserve|@license|@cc_on/i註釋
          //  感覺沒什麼特殊情況直接設定為false即可
          extractComments: false,

          // 壓縮時的選項設定
          terserOptions: {
            //  是否保留原始函式名稱,true代表保留,false即保留
            //  此屬性對使用Function.prototype.name
            //  預設為false
            keep_fnames: false,

            // 是否保留原始類名稱
            keep_classnames: false,

            //  format和output是同一個屬性值,,名稱不一致,output不建議使用了,被放棄
            // 指定壓縮格式。例如是否保留*註釋*,是否始終為*if*、*for*等設定大括號。
            format: {
              comments: false,
            },
            output: undefined,

            //  是否支援IE8,預設不支援
            ie8: false,

            compress: {
              // 是否使用預設配置項,這個屬性當只啟用指定某些選項時可以設定為false
              defaults: false,

              // 是否移除無法訪問的程式碼
              dead_code: false,

              // 是否優化只使用一次的變數
              collapse_vars: true,

              warnings: true,

              //  是否刪除所有 console.*語句,預設為false,這個可以線上上設定為true
              drop_console: false,

              //  是否刪除所有debugger語句,預設為true
              drop_debugger: true,

              //  移除指定func,這個屬性假定函式沒有任何副作用,可以使用此屬性移除所有指定func
              // pure_funcs: ['console.log'], //移除console
            },
          },
        })
      ]
    },

    plugins: [
      new HtmlWebpackPlugin({
        //  HTML的標題,
        //  template的title優先順序大於當前資料
        title: 'my-cli',

        //  輸出的html檔名稱
        filename: 'index.html',

        //  本地HTML模板檔案地址
        template: path.join(config.root, 'src/index.html'),

        // 引用JS檔案的目錄路徑
        publicPath: './',

        //  引用JS檔案的位置
        //  true或者body將打包後的js指令碼放入body元素下,head則將指令碼放到中
        //  預設為true
        inject: 'body',

        //  載入js方式,值為defer/blocking
        //  預設為blocking, 如果設定了defer,則在js引用標籤上加上此屬性,進行非同步載入
        scriptLoading: 'blocking',

        //  是否進行快取,預設為true,在開發環境可以設定成false
        cache: false,

        //  新增mate屬性
        meta: {}
      }),

      new CleanWebpackPlugin({
        // 是否假裝刪除檔案
        //  如果為false則代表真實刪除,如果為true,則代表不刪除
        dry: false,

        //  是否將刪除日誌列印到控制檯 預設為false
        verbose: true,

        //  允許保留本次打包的檔案
        //  true為允許,false為不允許,保留本次打包結果,也就是會刪除本次打包的檔案
        //  預設為true
        protectWebpackAssets: true,

        //  每次打包之前刪除匹配的檔案
        cleanOnceBeforeBuildPatterns: ['**/*'],

        //  每次打包之後刪除匹配的檔案
        cleanAfterEveryBuildPatterns:["*.js"],
      }),

      new webpack.DefinePlugin({ "global_a": JSON.stringify("我是一個打包配置的全域性變數") }),
    ],

    resolve: {
      alias: {
        //  設定路徑別名
        '@': path.join(config.root, 'src'),

        '~': path.join(config.root, 'src/assets'),
      },
      //  可互忽略的字尾
      extensions: ['.jsx', '.js', '.json'],
      //  預設讀取的檔名
      mainFiles: ['index', 'main'],
    }
  }
}

webpack.common.js 檔案匯出的是一個函式 ,函式引數是一個是一個boolean型別的isDev,判斷當前模式。將細小化的差異直接 webpack.common.js 檔案中。

例如: output.filename 中是否快取和 optimization.minimize 屬性

??? 在webpack.common.js內部根據isDev設定了browserslist使用的環境變數

然後,就可以在 webpack.dev.jswebpack.pro.js 檔案中使用webpack-merge 進行合併 webpack.common.js 檔案中模組

webpack.dev.js

const { merge } = require('webpack-merge');
const common = require('./webpack.common');
//  使用node.js的匯出,將配置進行匯出
module.exports = merge([
  common(true),
  {
    mode:'development',
  }
])

webpack.pro.js

const { merge } = require('webpack-merge');
const common = require('./webpack.common');
//  使用node.js的匯出,將配置進行匯出
module.exports = merge([
  common(false),
  {
    mode:'production',
  }
])

可以看到 程式碼中直接呼叫了webpack-merge提供的一個 merge 函式合併然後匯出。 merge 函式會將傳入的多個物件屬性進行合併,然後返回合併後物件。

merge 函式其實與 Object.assign() 功能一樣,對多個JS物件屬性進行合併。只不過 merge 功能較為強大一些。

??? webpack配置匯出的是一個JS物件,在程式碼中可以以任意形式組織此物件,只要最後進行匯出就行。

? merge函式引數可以使用陣列形式傳遞,並且支援可變引數。

總結

???

  • 開發模式(development)釋出模式(production)進行拆分主要是為了更方便管理程式碼,其具體結構依照每個人編碼習慣具有差異性
  • webpack-merge 是一個將多個JS物件屬性合併的庫,功能與 Object.assign() 一致,只是功能更為強大一些。

如果此篇對您有所幫助,在此求一個star。專案地址: OrcasTeam/my-cli

本文參考

本文依賴

package.json

{
  "name": "my-cli",
  "version": "1.0.0",
  "main": "index.js",
  "author": "mowenjinzhao<yanzhangshuai@126.com>",
  "license": "MIT",
  "devDependencies": {
    "@babel/core": "7.13.1",
    "@babel/plugin-transform-runtime": "7.13.7",
    "@babel/preset-env": "7.13.5",
    "@babel/preset-react": "7.12.13",
    "@babel/runtime-corejs3": "7.13.7",
    "babel-loader": "8.2.2",
    "clean-webpack-plugin": "3.0.0",
    "html-webpack-plugin": "5.2.0",
    "webpack": "5.24.0",
    "webpack-cli": "4.5.0",
    "webpack-merge": "5.7.3"
  },
  "dependencies": {
    "react": "17.0.1",
    "react-dom": "17.0.1",
  },
      
  "scripts": {
    "start": "webpack-dev-server  --config build/webpack.dev.js",
    "build": "webpack  --config build/webpack.pro.js",
  },
      
 "browserslist": {
    "development": [
      "chrome > 75"
    ],
    "production": [
      "ie 9"
    ]
  }
}

相關文章