webpack打包原理和基本配置

webpack簡介

webpack其實是一個靜態模塊打包工具css

webpack 處理項目時,它會遞歸地構建一個依賴關係圖(dependency graph),其中包含應用程序須要的每一個模塊,而後將全部這些模塊打包成一個或多個 bundle。html

打包原理

image

  • 識別入口文件
  • 經過逐層識別模塊依賴。(Commonjs、amd或者es6的import,webpack都會對其進行分析。來獲取代碼的依賴)
  • webpack作的就是分析代碼。轉換代碼,編譯代碼,輸出代碼
  • 最終造成打包後的代碼

打包調試命令

npm run dev

npm run build
複製代碼

packages.json

...
"scripts": {
  "dev_def": "webpack-dev-server --inline --public --config build/dev.js",
  "dev": "nodemon --watch config/index.js --exec \"webpack-dev-server --inline --public --config build/dev.js\"",
  "start": "npm run dev",
  "build": "cross-env NODE_ENV=production node build/build.js"
}
...
複製代碼

webpack-dev-server

是一個輕量級的服務器,修改文件源碼後,自動刷新頁面將修改同步到頁面上vue

webpack-dev-server --inline --public --config build/dev.js
複製代碼
  • 【--inline 或者 --inline=false】 內聯模式:將在包中插入腳本以處理實時從新加載,而且構建消息將顯示在瀏覽器控制檯中.
module.exports = {
  //...
  devServer: {
    inline: true
  }
};
複製代碼
  • 【--public xxx】使用內聯模式而且正在代理dev-server時,內聯客戶端腳本並不老是知道鏈接到哪裏。它將嘗試根據服務器的URL來猜想window.location,但若是失敗則須要使用它
  • 【--config xxx】指定配置文件
  • 【--progress】輸出運行進度到控制檯。

nodemon

會監測項目中的文件,一旦發現文件有改動,Nodemon 會自動重啓應用node

  • 【--watch xxx】 監控指定的文件或者目錄
  • 【--exec xxx】 執行指定的命令
nodemon --watch config/index.js --exec \"webpack-dev-server --inline --public --config build/dev.js\" 複製代碼

這句話的意思就是: 用nodemon監控config/index.js文件,若是有變化,則從新執行【webpack-dev-server --inline --public --config build/dev.js】的命令linux

而【webpack-dev-server --inline --public --config build/dev.js】命令對項目自己具備熱更新功能,但webpack配置文件修改時,dev-sever自己不會生效。而用nodemon就是在webpack配置文件修改時也重啓服務,算是一個自動補充webpack

cross-env

解決跨平臺設置和使用環境變量的腳本,如變量名稱、路徑方面的抹平es6

  • cross-env NODE_ENV=production 抹平了跨平臺環境變量的設置問題

基本配置

module.exports = {
  // 入口文件
  entry: {
    app: './src/js/index.js'
  },
  // 在哪裏輸出它所建立的 bundles
  output: {
    filename: '[name].bundle.js',
    path: path.resolve(__dirname, 'dist'),
    publicPath: '/'     //確保文件資源可以在 http://localhost:3000 下正確訪問
  },
  // 開發者工具 source-map
  devtool: 'inline-source-map',
  // 建立開發者服務器
  devServer: {
    contentBase: './dist',
    hot: true                // 熱更新
  },
  plugins: [
    // 刪除dist目錄
    new CleanWebpackPlugin(['dist']),
    // 從新穿件html文件
    new HtmlWebpackPlugin({
      title: 'Output Management'
    }),
    // 以便更容易查看要修補(patch)的依賴
    new webpack.NamedModulesPlugin(),
    // 熱更新模塊
    new webpack.HotModuleReplacementPlugin()
  ],
  // 環境
  mode: "development",
  // loader配置
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [
          'style-loader',
          'css-loader'
        ]
      },
      {
        test: /\.(png|svg|jpg|gif)$/,
        use: [
          'file-loader'
        ]
      }
    ]
  }
}
複製代碼

__dirname: 當前文件所在文件夾的絕對路徑web

entry(入口文件配置)

單個入口語法(簡寫)

entry: './path/to/my/entry/file.js'

// 或者(對象寫法)

entry: {
  main: './path/to/my/entry/file.js'
}
複製代碼

多頁面應用程序

entry: {
  pageOne: './src/pageOne/index.js',
  pageTwo: './src/pageTwo/index.js',
  pageThree: './src/pageThree/index.js'
}
複製代碼

注意:webpack以前的寫法(不推薦)

entry:{
  vendor:[resolve('src/lib/polyfill.js'), 'vue', 'vue-router'], // 不推薦
  app: resolve('src/main.ts')
}
複製代碼

在webpack4以前的版本中,一般將供應商添加爲單獨的入口點,以將其編譯爲單獨的文件vendor(與之結合使用CommonsChunkPlugin)vue-router

在webpack 4中不鼓勵這樣作。相反,該optimization.splitChunks選項負責分離供應商和應用程序模塊並建立單獨的文件。不要爲供應商或其餘不是執行起點的東西建立條目。npm

output(輸出文件配)

output: {
  filename: '[name].bundle.js',
  chunkFilename: [name].min.js,
  path: path.resolve(__dirname, 'dist'),
  publicPath: '/'     //確保文件資源可以在 http://localhost:3000 下正確訪問
}
複製代碼
  • filename: 輸出文件名
  • chunkFilename:此選項決定了非入口文件的名稱
  • path:全部輸出文件的目標路徑
  • publicPath: 指定資源文件引用的目錄,build後的文件,資源引用路徑前綴

devtool(調試工具:文件映射)

// dev
devtool: 'eval-source-map'

// prod
devtool: 'source-map'
複製代碼

關鍵字揭祕:

關鍵字 含義
eval 在打包的時候,生成的bundle.js文件,模塊都被eval包裹,而且後面跟着sourceUrl,指向的是原文件
source-map 這種配置會生成一個帶有.map文件,這個map文件會和原始文件作一個映射,調試的時候,就是經過這個.map文件去定位原來的代碼位置的
cheap 低消耗打包,就是打包的時候map文件,不會保存原始代碼的列位置信息,只包含行位置信息,因此這就解釋官網圖後面的說明(僅限行)
... ...

devServer

devServer: {
  compress: true,
  port: 9000,
  hot: true,
  https: true,
  overlay: {
    warnings: false,
    errors: true
  },
  publicPath: '/platform/redapply/'
}
複製代碼
  • compress:啓用gzip壓縮
  • port: 端口號
  • hot:是否啓用Hot Module Replacement特性
  • https:可使用自簽名的證書,一樣能夠自定義簽名證書 | boolean、object
  • overlay:在瀏覽器上全屏顯示編譯的errors或warnings | boolean、object
  • publicPath:打包的文件將被部署到該配置對應的path。http://localhost:8080/platform/redapply/index.html

mode(告訴webpack相應地使用其內置優化)

// dev
mode: 'development'

// build
mode: 'production'
複製代碼

plugins(插件)

plugins: [
  new webpack.DefinePlugin({
    'process.env': {
       NODE_ENV: '"production"'
     }
  }),
]
複製代碼

module.rules(loader配置)

module: {
  rules: [
    {
      test: /\.css$/,
      use: [
        'style-loader',
        'css-loader'
      ]
    },
    {
      test: /\.(png|svg|jpg|gif)$/,
      use: [
        'file-loader'
      ]
    }
  ]
}
複製代碼

對於loader的執行順序,是從後往前的

項目經常使用配置

resolve(解析)

resolve: {
  extensions: ['.js','.ts', '.vue', '.json'],
  alias: {
    '@lib': resolve('src/lib'),
    '@models': resolve('build/models'),
    '@components': resolve('src/components'),
    '@data': resolve('src/data'),
    '@': resolve('src')
  }
}
複製代碼
  • extensions 解析路徑缺省文件名時候的後綴(按順序依次嘗試)
  • alias 自定義路徑符號定義

預加載文件

好比定義一些公共的scss文件。爲了避免再每一個頁面都引入該文件。咱們能夠設置文件預加載

module: {
  rules: [
    ...
    {
      test: /\.sass|scss|css$/,
      use: [
        ...
        {
          loader: 'sass-resources-loader',
          options: {
            resources: [
              path.resolve(__dirname, '../src/assets/css/vars.scss'),
              path.resolve(__dirname, '../src/assets/css/common.scss')
            ]
          }
        }
      ]
    }
  ]
}
複製代碼

optimization(優化配置項,build時配置)

optimization: {
    minimize: true,                         // 默認爲true,效果就是壓縮js代碼。
    minimizer: [                            // 壓縮時調用的插件
      new TerserPlugin(),
      new OptimizeCSSAssetsPlugin({})
    ],
    runtimeChunk: {                         // 默認爲false,抽離出運行時公共代碼塊。
      name: 'manifest'
    },
    splitChunks:{
      chunks: 'all',                        // 必須三選一: "initial" | "all"(推薦) | "async" (默認就是async)
      minSize: 30000,                       // 生成塊的最小字節數,30000
      minChunks: 1,                         // 最少被引用的次數
      maxAsyncRequests: 3,                  // 按需加載時候最大的並行請求數
      maxInitialRequests: 3,                // 一個入口最大的並行請求數
      name: true,                           // 打包的chunks的名字
      cacheGroups: {                        // 緩存配置
        common: {
          name: 'common',                   // 要緩存的 分隔出來的 chunk 名稱
          chunks: 'initial',                // 必須三選一: "initial" | "all" | "async"(默認就是async) 
          priority: 11,
          enforce: true,
          reuseExistingChunk: true,         // 可設置是否重用該chunk
          test: /[\/]node_modules[\/](vue|babel\-polyfill|mint\-ui)/
        },
        vendor: {                           // key 爲entry中定義的 入口名稱
          name: 'vendor',                   // 要緩存的 分隔出來的 chunk 名稱
          chunks: 'initial',                // 必須三選一: "initial" | "all" | "async"(默認就是async) 
          priority: 10,
          enforce: true,
          reuseExistingChunk: true,         // 可設置是否重用該chunk
          test: /node_modules\/(.*)\.js/
        },
        styles: {
          name: 'styles',
          test: /\.(scss|css)$/,
          chunks: 'all',
          minChunks: 1,
          reuseExistingChunk: true,
          enforce: true
        }
      }
    }
  }
複製代碼

runtimeChunk

默認爲false, 抽離出運行時公共代碼塊

什麼是運行時(runtime)?

JS在瀏覽器中能夠調用瀏覽器提供的API,如window對象,DOM相關API等。這些接口並非由V8引擎提供的,是存在與瀏覽器當中的。所以簡單來講,對於這些相關的外部接口,能夠在運行時供JS調用,以及JS的事件循環(Event Loop)和事件隊列(Callback Queue),把這些稱爲RunTime。有些地方也把JS所用到的core lib核心庫也看做RunTime的一部分。

chunk運行時

在chunk執行的時候所依賴的環境(方法)

splitChunks

chunks

function (chunk) | string

這表示將選擇哪些塊進行優化

string:

  • initial - 入口chunk,對於異步導入的文件不處理
  • async - 異步chunk,只對異步導入的文件處理(我的理解)
  • all - 所有chunk

function:

splitChunks: {
  chunks (chunk) {
    // exclude `my-excluded-chunk`
    return chunk.name !== 'my-excluded-chunk';
  }
}
複製代碼

cacheGroups

緩存組能夠繼承和/或覆蓋任何選項splitChunks.*;要禁用任何默認緩存組,請將其設置爲false。

  • priority 優先級,模塊能夠屬於多個緩存組,單最終會被打入優先級高的chunk
  • reuseExistingChunk 表示可使用已經存在的塊,即若是知足條件的塊已經存在就使用已有的,再也不建立一個新的塊
  • enforce minSize,minChunks,maxInitialRequests選項,爲快速建立chunk用
  • test 緩存組的規則,表示符合條件的的放入當前緩存組
optimization: {
  splitChunks: {
    chunks: 'async',
    minSize: 30000,
    minChunks: 1,
    maxAsyncRequests: 3,
    maxInitialRequests: 3,
    name: true,
    cacheGroups: {
      common: {
        name: 'common',
        chunks: 'initial',
        priority: 11,
        enforce: true,
        reuseExistingChunk: true,         // 可設置是否重用該chunk
        test: /[\/|\\]node_modules[\/|\\](vue|babel\-polyfill|mint\-ui)/
      },
      vendor: {
        name: "vendor",
        chunks: "initial",
        priority: 10,
        test: /[\/|\\]node_modules[\/|\\](.*)\.js/
      },
      styles: {
        name: 'styles',
        test: /\.(scss|css|less)$/,
        chunks: 'initial',
        minChunks: 1,
        reuseExistingChunk: true,
        enforce: true
      }
    }
  }
}
複製代碼

注意:

這裏有個坑,就是關於test匹配路徑的問題 通常網上看到的如:

...
common: {
  name: 'common',
  chunks: 'initial',
  priority: 11,
  enforce: true,
  reuseExistingChunk: true,
  test: /[\/]node_modules[\/](vue|babel\-polyfill|mint\-ui)/
}
...
複製代碼

這裏面test匹配正則是根據linux環境路徑匹配的。(如:node_modules/vue)

但window路徑和linux路徑不同,它是反斜槓。(如:node_modules\vue)

這樣咱們要把正則改爲

/[\/|\\]node_modules[\/|\\](vue|babel\-polyfill|mint\-ui)/
複製代碼

這樣就能夠兼容兩種環境了

項目中的配置

一般來說webpack就須要3個配置文件

  • webpack.base.conf.js // 公共配置
  • webpack.dev.conf.js // 開發環境配置
  • webpack.prod.conf.js // 生產環境配置

但在咱們的項目裏你們看到的配置和上面介紹的並不徹底相同。

是由於:咱們在腳手架生成項目的時候,已經集成了webpack的基本配置。

它們都在@zz/webpack-vue下

image

而咱們項目中僅暴露了一些配置對象入口。

image

暴露出來的配置項結構和webpack自己的略有區別。

開發人員能夠經過自定義這些對象,而後程序會和默認的配置進行合併,造成最終的配置參數。

(本文爲內部專題學習webpack的一次分享,內容比較基礎)

相關文章
相關標籤/搜索