整合營銷服務(wù)商

          電腦端+手機端+微信端=數(shù)據(jù)同步管理

          免費咨詢熱線:

          多頁面項目webpack打包實踐

          在最近的項目開發(fā)中,涉及到了多頁面的 webpack 打包,以下是我項目過程中的一些踩坑總結(jié)。

          前言

          項目使用了 vue 作為框架來開發(fā)前端頁面,其中需要開發(fā)多個前端頁面,包括有登錄、進游戲、充值等等。作為vue最佳的打包工具—— webpack,需要將各個頁面分別打包到不同模板目錄里。

          但默認的 vue 項目框架是單頁面應(yīng)用的,并不能達到項目開發(fā)的目的。這就需要調(diào)整 webpack 的配置來實現(xiàn)多頁面的發(fā)布處理。

          以下是目錄結(jié)構(gòu):

          project
          ├───bin
          │   └───vb.js
          ├───build
          │   │   dev.js
          │   │   release.js
          │   │   webpack.config.base.js
          │   │   webpack.config.build.js
          │   └───webpack.config.dev.js
          │   README.md
          │   package.json
          └───src
              ├───components
              │   │   count.vue
              │   │   dialog.vue
              │   │   errortips.vue
              │   └───...
              ├───game
              │   │   game.htm
              │   │   game.js
              │   └───game.vue
              ├───login
              │   │   login.htm
              │   │   login.js
              │   └───login.vue
              ├───pay
              │   │   pay_result.htm
              │   │   pay_result.js
              │   │   pay_result.vue
              │   │   pay.htm
              │   │   pay.js
              │   └───pay.vue
              └───...


          修改配置前的一些知識

          我們知道webpack的核心是一切皆模塊,所以它本質(zhì)上是一個靜態(tài)模塊打包器。當 webpack 處理應(yīng)用程序時,它會遞歸地構(gòu)建一個依賴關(guān)系圖,其中包含應(yīng)用程序需要的每個模塊,然后將所有這些模塊打包成一個或多個 bundle。

          官網(wǎng)顯示的這幅圖很形象地描述了這個過程。


          從 webpack v4.0.0 開始,webpack 提供了一系列的配置默認項,讓開發(fā)者可以零配置打包,不再強制要求必須進行繁瑣的 webpack 配置,讓開發(fā)者可以從繁瑣的配置文件里抽出,專注應(yīng)用的開發(fā)。但是若你需要有特殊的處理,webpack 仍然可以進行高度可配置來滿足你的需求。

          在開始前需要了解四個核心概念:

          • 入口(entry):指示 webpack 應(yīng)該使用哪個模塊,來作為構(gòu)建其內(nèi)部依賴圖的開始。


          • 輸出(output):指示 webpack 在哪里輸出它所創(chuàng)建的 bundles,以及如何命名這些文件


          • loader:讓 webpack 能夠去處理那些非 JavaScript 文件


          • 插件(plugins):可以讓 webpack 執(zhí)行范圍更廣的任務(wù)。


          本篇將會針對這4個核心配置的修改和優(yōu)化來實現(xiàn)多頁面打包。在 webpack4 的版本,還新增了一個 mode 配置項。mode 有兩個值:development 或者是 production,用戶可以啟用相應(yīng)模式下的 webpack 內(nèi)置的優(yōu)化。不同 mode 的區(qū)別與默認配置可以參考:https://segmentfault.com/a/1190000013712229


          一、入口配置

          在單頁面應(yīng)用里,一般在根目錄下面會有一個 index.html 文件。它是頁面的 html 模板文件。但是在多頁面應(yīng)用里,則會有多個應(yīng)用模板文件,為了方便管理,可以將不同類的入口文件、邏輯處理和模板文件分別存放在相應(yīng)的獨立目錄。若用到了組件,則單獨將組件存放在一個目錄。

          project
          └───src
              ├───components
              │   │   count.vue
              │   │   dialog.vue
              │   │   errortips.vue
              │   └───...
              ├───game
              │   │   game.htm
              │   │   game.js
              │   └───game.vue
              ├───login
              │   │   login.htm
              │   │   login.js
              │   └───login.vue
              ├───pay
              │   │   pay_result.htm
              │   │   pay_result.js
              │   │   pay_result.vue
              │   │   pay.htm
              │   │   pay.js
              │   └───pay.vue
              └───...


          webpack 的入口配置中是支持多入口的,給 entry 傳入對象即可,如下所示:

          const config = {
            entry: {
              game: './src/game/game.js',
              login: './src/login/login.js',
              pay: './src/pay/pay.js',
              pay_result: './src/pay/pay_result.js'
            }
          };

          但這樣的配置對于未知頁面數(shù)量的項目并不友好,若每新增頁面都要重新配置和重啟程序,顯然是不合理的。而我們可以創(chuàng)建一個getEntry()的方法來遍歷文件夾來獲取入口。

          const fs = require('fs');
          const glob = require("glob"); 
          function getEntry() {
              const entry = {};
              //讀取src目錄所有page入口
              glob.sync('./src/*/*.js') //獲取符合正則的文件數(shù)組
                  .forEach(function (filePath) {
                      var name = filePath.match(/\/src\/(.+)\/*.js/);
                      name = name[1];
                      //須有配套的模板文件才認為是入口
                      if (!fs.existsSync('./src/' + name + '.htm')) {
                          return;
                      }
                      entry[name] = filePath;
                  });
              return entry;
          };
          module.exports = {
            // 多入口
            entry: getEntry(),
          }


          二、輸出配置

          輸出配置僅需指定一個

          const config = {
              output: {
                  path: path.join(__projectDir, __setting.distJs),
                  publicPath: __setting.domainJs, //自定義變量,用來定義公共靜態(tài)資源路徑
                  filename: '[name][hash].js'
              },
          };
          • path:目標輸出目錄的絕對路徑
          • publicPath:文件中靜態(tài)資源的引用路徑

          https://www.webpackjs.com/configuration/output/#output-publicpath

          • filename:用于輸出文件的文件名

          https://www.webpackjs.com/configuration/output/#output-filename

          在配置中有以下幾點需要注意:

          • publicPath

          publicPath 是指定在瀏覽器中所引用的「此輸出目錄對應(yīng)的公開 URL」。

          簡單的例子:

          publicPath: "https://cdn.example.com/assets/"

          輸出到html則變成

          <script src="https://cdn.example.com/assets/bundle.js"></script>

          這個屬性是整個項目共用的靜態(tài)資源路徑,若某個模塊需要使用其他的靜態(tài)資源路徑。webpack 提供了__webpack_public_path__來動態(tài)設(shè)置 publicPath,只需在入口文件的最頂部定義即可。

          __webpack_public_path__ = myRuntimePublicPath; // 一定要寫在最頂部
          • hash

          filename的[hash]是以項目為維度的 hash 值,若輸出了多個文件,則文件名都會共用一個 hash 值。

          filename的[chunkhash]是以chunk為維度生成的 hash 值,不同入口生成不同的 chunkhash 值。

          filename的[contenthash]根據(jù)資源內(nèi)容生成的 hash 值。

          通常使用 hash 或 chunkhash,contenthash 通常用于某些特殊場景(官方文檔在使用 ExtractTextWebpackPlugin 插件時有使用)。

          https://www.webpackjs.com/plugins/extract-text-webpack-plugin/


          三、loader配置

          由于 webpack 只能理解 JavaScript 和 JSON 文件。而配置 loader 就是讓 webpack 能夠去處理其他類型的文件,并將它們轉(zhuǎn)換為有效模塊。

          loader 可以使開發(fā)者在 import 或"加載"模塊時預(yù)處理文件。例如,將內(nèi)聯(lián)圖像轉(zhuǎn)換為 data URL,或者允許開發(fā)者直接在 JavaScript 模塊中 import CSS文件

          1、js 模塊

          加載js模塊,我們通常是為了引入babel,讓其能將ES6的語法轉(zhuǎn)成ES5,讓項目能在低版本的瀏覽器上運行。

          js文件需要使用babel的話,引入babel-loader

          const config = {
              module: {
                  rules: [{
                      test: /\.js$/,
                      include: [path.resolve(__projectDir, 'src')], //通過include精確指定只處理哪些目錄下的文件
                      exclude: /node_modules/, //設(shè)置哪些目錄里的文件不進行處理
                      loader: "babel-loader"
                  }]
              }
          }

          但僅僅配置了babel-loader還不夠,還需要配置 babel 的環(huán)境,需要引入 polyfill。

          引入 polyfill 的方式有很多種,根據(jù) vue 官方文檔在瀏覽器兼容性的處理,默認使用的是@vue/babel-preset-app ,它通過@babel/preset-env和browserslist配置來決定項目需要的 polyfill。

          https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/babel-preset-app


          browserslist

          項目根目錄創(chuàng)建.browserslist文件

          > 1%
          last 2 versions

          當然,你也可以在package.json文件里添加的browserslist字段來配置。

          這個配置的目的是為了指定了項目的目標瀏覽器的范圍,配置的值會被 @babel/preset-env 用來確定需要轉(zhuǎn)譯的 JavaScript 特性。

          詳細的說明可以查閱 https://github.com/browserslist/browserslist,了解如何指定瀏覽器范圍。

          Polyfill

          項目根目錄創(chuàng)建.babelrc文件

          {
            "presets": [
              ["@babel/preset-env",
                {
                  "modules": false, // 對ES6的模塊文件不做轉(zhuǎn)化,以便使用tree shaking、sideEffects等
                  "useBuiltIns": "entry", // browserslist環(huán)境不支持的所有墊片都導(dǎo)入
                  "corejs": {
                    "version": 3, // 使用core-js@3
                    "proposals": true
                  }
                }
              ]
            ]
          }

          這里特別說下的是配置里的useBuiltIns,可設(shè)置的值分別是"usage" | "entry" | false,3個值分別代表:

          • usage 會根據(jù)配置的瀏覽器兼容,以及你代碼中用到的 API 來進行 polyfill,實現(xiàn)了按需添加。
          • entry 根據(jù)配置的瀏覽器兼容,引入瀏覽器不兼容的 polyfill。這時會自動根據(jù) browserslist 替換成瀏覽器不兼容的所有 polyfill。
          • false 此時不對 polyfill 做操作。如果引入@babel/polyfill,則無視配置的瀏覽器兼容,引入所有的 polyfill。

          項目使用的是"useBuiltIns": "entry",所以需要指定corejs的版本,這里使用的版本是core-js@3,所以我們在 webpack 的入口配置里加上"core-js/stable"和 "regenerator-runtime/runtime"。

          function getEntry() {
              const entry = {};
              //讀取src目錄所有page入口
              glob.sync('./src/*/*.js') //獲取符合正則的文件數(shù)組
                  .forEach(function (filePath) {
                      var name = filePath.match(/\/src\/(.+)\/*.js/);
                      name = name[1];
                      //須有配套的模板文件才認為是入口
                      if (!fs.existsSync('./src/' + name + '.htm')) {
                          return;
                      }
                      entry[name] = ["core-js/stable", "regenerator-runtime/runtime", path.join(__projectDir, filePath)];
                  });
              return entry;
          };


          2、css 模塊

          我們通常使用style-loader和css-loader。css-loader用來處理 js 文件中引入的 css 模塊(處理@import和url()),style-loader是將css-loader打包好的css代碼以<style>標簽的形式插入到 html 文件中。而 webpack 對于 loader 的調(diào)用是從右往左的,所以通常是這樣配置:

          {
              test: /\.css$/,
              use: [ 'style-loader', 'css-loader' ]
          }

          我們在項目中還經(jīng)常會使用 sass 或者 scss。sass 是一種 CSS 的預(yù)編譯語言。因此 webpack 要將其處理會使用更多 loader。

          {
              test: /\.(sc|sa)ss$/,
              use: [{
                  loader: 'vue-style-loader'
              }, {
                  loader: 'css-loader',
                  options: {
                      sourceMap: true,
                  }
              }, {
                  loader: 'postcss-loader',
                  options: {
                      sourceMap: true
                  }
              }, {
                  loader: 'sass-loader',
                  options: {
                      sourceMap: true
                  }
              }, {
                  loader: 'sass-resources-loader', //組件里面使用全局scss
                  options: {
                      sourceMap: true,
                      resources: [
                          path.resolve('./src/public/css/common.scss')
                      ]
                  }
              }]
          }

          在使用sass-loader的時候若某個 scss 文件(比如a.scss)@import 了其他 scss 文件(比如b.scss),如果b.scss里的url()的路徑是相對路徑,在sass-loader處理過后給css-loader處理時就會報錯,找不到url()里指定的資源。

          這是因為sass-loader處理時,會將 scss 文件里 @import 路徑的文件一并合并進來,結(jié)合上面的例子就是b.scss會被sass-loader合并進a.scss。

          如何解決呢?可以有兩個解決方法:

          • 將資源路徑改為變量來統(tǒng)一管理
          • 通過 alias 設(shè)置路徑別名,從而便捷使用絕對路徑。注意在scss文件中使用 alias 里定義的路徑別名時,需要帶上~前綴,否則打包時仍會被識別為普通路徑。

          在項目中由于還用到了postcss-loader,我們還須要在根目錄創(chuàng)建postcss-loader的配置文件postcss.config.js

          //自動添加css瀏覽器前綴
          module.exports = {
              plugins: [
                  require('autoprefixer')
              ]
          }


          3、圖片等靜態(tài)資源

          對于圖片資源的打包,經(jīng)常會使用file-loader來完成,配置也很簡單:

          {
            test: /\.(gif|png|jpe?g)$/,
            loader: 'file-loader',
          }

          打包后,會將圖片移動到了 dist 目錄下,并將該圖片改名為[hash].[ext]格式的圖片。開發(fā)者也可以根據(jù)需要,修改輸出的文件名。

          但在項目開發(fā)過程中,我們會創(chuàng)建很多張圖片,這就使得頁面在加載是時候會發(fā)送很多http請求,當頁面圖片過多,會影響的頁面的性能。所以,這里推薦使用url-loader。

          {
              test: /\.(png|jpg|jepg|svg|gif)$/,
              use: [{
                  loader: 'url-loader',
                  options: {
                      limit: 10240, //這里的單位是b
                      name: 'image/[name][hash].[ext]' //打包后輸出路徑
                  }
              }]
          }

          使用url-loader我們可以通過設(shè)置limit的值,將文件大小小于某個值的圖片打包成base64的形式存放在打包后的 js 中,若超過了這個設(shè)定值,默認會使用file-loader(所以雖然代碼沒有配置 file-loader,但還是需要使用安裝file-loader),并且會將配置的選項傳遞給file-loader。


          4、import AMD 模塊

          有時我們需要在項目里使用一些 AMD 模塊或者完全不支持模塊化的庫。例如移動端經(jīng)常使用的 zepto。如果我們直接使用 import zepto 的方式引入是會報錯的:Uncaught TypeError: Cannot read property 'createElement' of undefined

          要使用也很簡單,使用script-loader和exports-loader即可:

          {
              test: require.resolve('zepto'),
              use: ['exports-loader?window.Zepto','script-loader']
          }
          • script-loader 用 eval 的方法將 zepto 在引入的時候執(zhí)行了一遍,此時 zepto 庫已存在于 window.Zepto
          • exports-loader 將傳入的 window.Zepto 以 module.exports = window.Zepto 的形式向外暴露接口,使這個模塊符合 CommonJS 規(guī)范,支持 import 這樣我們就可以直接import $ from 'zepto'了,其他 AMD 模塊或者其他不支持模塊化的庫也類似。



          四、plugins

          webpack 可以使用插件(plugins)來讓開發(fā)者能夠在打包的過程中實現(xiàn)更多功能,插件會在整個構(gòu)建過程中生效,并執(zhí)行相關(guān)的任務(wù)。這里會介紹幾個比較實用的插件:

          1、mini-css-extract-plugin

          在使用style-loader處理后,css 文件會作為模塊打包進 js 文件里。若我們想將 js 文件和 css 文件分離。就可以使用mini-css-extract-plugin:

          module: {
              rules: [{
                  test: /\.css$/,
                  use: [{
                      loader: MiniCssExtractPlugin.loader
                  },
                      'css-loader'
                  ]
              }]
          },
          plugins: [
              new MiniCssExtractPlugin({
                  filename: 'css/[hash].css'
              })
          ]


          2、copy-webpack-plugin

          有時候我們會有一些沒經(jīng)過打包的文件需要復(fù)制到我們的生產(chǎn)目錄里,copy-webpack-plugin就可以實現(xiàn)這個功能。

          plugins: [
              new CopyWebpackPlugin([
                  {
                      from: { glob: './src/public/*.htm', dot: true },
                      to: path.join(__setting.distTpl, 'public','[name].htm')
                  }
              ], { copyUnmodified: true })
          ]


          3、html-webpack-plugin

          我們前面介紹入口配置的時候會看到只配置了 js 文件,只是因為 webpack 現(xiàn)在入口只支持 js 文件,所以打包輸出的也是 js 文件,那如果我們需要將 js 文件引入到 html 里,就需要使用到html-webpack-plugin插件。

          html-webpack-plugin在使用的時候,是必須一個入口對應(yīng)一個配置的,所以我們前面使用了多頁面的配置,也需要進行相應(yīng)的修改,修改后的getEntry方法:

          const htmlPluginArray = [];
          function getEntry() {
              const entry = {};
              //讀取src目錄所有page入口
              glob.sync('./src/' + __setting.moduleId + '/*.js')
                  .forEach(function (filePath) {
                      var name = filePath.match(/\/src\/(.+)\/*.js/);
                      name = name[1];
                      if (!fs.existsSync(path.join(__projectDir, './src/' + name + '.htm'))) {
                          return;
                      }
                      entry[name] = ["core-js/stable", "regenerator-runtime/runtime", path.join(__projectDir, filePath)];
          +           htmlPluginArray.push(new HtmlWebpackPlugin({
          +               filename: `${__setting.distTpl}/${name}.htm`,
          +               template: './src/' + name + '.htm',
          +               inject: 'body',
          +               minify: {
          +                   removeComments: true,
          +                   collapseWhitespace: true
          +               },
          +               chunks: [name],
          +               inlineSource: '.(js|css)'
          +           }))
                  });
              return entry;
          };
          
          
          // 配置plugin,由于plugins通常使用數(shù)組類型來配置,
          // 所以可以使用concat方法將配置好的html的數(shù)組添加進去。
          plugins: [
              new MiniCssExtractPlugin({
                  filename: 'css/[hash].css'
              })
          ].concat(htmlPluginArray),


          里面的一些配置是要注意一下的:

          • filename

          filename 是配置需要將 html 改成什么名字并輸出到哪里的配置。這里配置的的路徑是以 output 里配置的path為相對路徑的,我們上面 output 配置的是

          path: path.join(__projectDir, __setting.distJs)

          那最終的html輸出路徑就是

          path.join(__projectDir, __setting.distJs, 
          `${__setting.distTpl}/${name}.htm`)
          • minify

          是將html里的代碼進行壓縮。如果 minify 選項設(shè)置為 true 或者配置對象 ( true 是 webpack 模式為 production 時的默認值),生成的 HTML 將使用 HTML-minifier壓縮代碼,更多具體的配置可以看這里minification。

          • 其他

          template 生成 filename 文件的模版。重點:與 filename 的路徑不同, 當匹配模版路徑的時候?qū)捻椖康母窂介_始。

          inject 制定 webpack 打包的 js css 靜態(tài)資源插入到 html 的位置。

          chunks 指定模板允許添加哪個入口文件。若不配置這個會將所有的入口都添加進來。


          4、html-webpack-inline-source-plugin

          若我們想將打包好的 js 代碼 inline 進 html 的話,就要使用到html-webpack-inline-source-plugin

          可以看到上面html-webpack-plugin的配置里有inlineSource: '.(js|css)'

          這就是告訴html-webpack-inline-source-plugin需要將打包好的代碼 inline 進 html 里,插件需要添加到html-webpack-plugin的配置后

          plugins: [
              new MiniCssExtractPlugin({
                  filename: 'css/[hash].css'
              })
          ].concat(htmlPluginArray).concat([
              new HtmlWebpackInlineSourcePlugin()
          ])

          但是html-webpack-inline-source-plugin也僅能將打包后輸出的 js 文件引入 html,若你想將 html 碼其他使用 script 標簽加載的 js 文件或者 style 標簽加載的 css 文件也 inline 進 html 里的話,html-webpack-inline-source-plugin并不能實現(xiàn)。從html-webpack-plugin里的 Issues 來看,html-webpack-plugin的作者也無意做這樣的事情,但也給出了建議,可以借助html-webpack-plugin插件的 hooks html-webpack-plugin-before-html-processing達到我們需要的效果。


          5、自定義插件

          上面說到要將外部的靜態(tài)文件也 inline 進 html,我們可以編寫自定義插件,借助html-webpack-plugin插件的 hooks html-webpack-plugin-before-html-processing,再結(jié)合inline-source組件來實現(xiàn)我們的功能。

          const {
              inlineSource
          } = require('inline-source');//加載inline-source組件
          //定義方法
          function scriptInlineHtml(options) {
              // Configure your plugin with options...
              this.options = options || {};
          }
          
          
          scriptInlineHtml.prototype.apply = function (compiler) {
              let that = this;
              (compiler.hooks ? //判斷webpack版本,4.0以上和4.0以下的處理不一樣
                  compiler.hooks.compilation.tap.bind(compiler.hooks.compilation, 'script-inline-html') :
                  compiler.plugin.bind(compiler, 'compilation'))(function (compilation) {
                      (compilation.hooks ?
                          compilation.hooks.htmlWebpackPluginBeforeHtmlProcessing.tapAsync.bind(compilation.hooks.htmlWebpackPluginBeforeHtmlProcessing, 'script-inline-html') :
                          compilation.plugin.bind(compilation, 'html-webpack-plugin-before-html-processing'))(async function (htmlPluginData, callback) {
          
          
                              //獲取的html內(nèi)容處理后重新賦值;
                              try {
                                  htmlPluginData.html = await inlineSource(htmlPluginData.html, that.options);
                                  // Do something with html
                              } catch (err) {
                                  // Handle error
                              }
                              //繼續(xù)執(zhí)行下個插件
                              callback(null, htmlPluginData);
                          });
                  });
          };
          //webpack插件添加
          plugins: [
              new MiniCssExtractPlugin({
                  filename: 'css/[hash].css'
              })
          ].concat(htmlPluginArray).concat([
              new scriptInlineHtml(),
              new HtmlWebpackInlineSourcePlugin()
          ])

          使用

          <script src="/src/public/js/px2rem.js" inline></script>

          這里結(jié)合 inline 靜態(tài)資源,簡單介紹了自定義插件的使用,在html-webpack-plugin構(gòu)建 html 過程中,還提供其他一系列的事件。

          Async:

          • html-webpack-plugin-before-html-generation
          • html-webpack-plugin-before-html-processing
          • html-webpack-plugin-alter-asset-tags
          • html-webpack-plugin-after-html-processing
          • html-webpack-plugin-after-emit

          Sync:

          • html-webpack-plugin-alter-chunks

          這些事件可以讓我們在構(gòu)建 html 的不同階段里,通過一些處理來達到我們的目的。例如:可以結(jié)合smarty.js將使用了 smarty 的模板,引入一些模擬數(shù)據(jù)后解析成正常的html代碼;讀取 HTML 文件進行翻譯文本的替換,實現(xiàn)頁面的多語言化。打包不同皮膚的html文件等等。



          五、其他配置

          1、resolve

          resolve 配置規(guī)定了 webpack 如何尋找各個依賴模塊。

          前面有講到使用 alias 設(shè)置路徑別名。在資源引用時,如果資源引用路徑太深,又比較常用,我們可以定義路徑別名,例如:

          resolve: {
              alias: {
                  '@': path.resolve(__projectDir, 'src')
              }
          }

          我們就可以直接在代碼中這樣引用了:

          let backimg = require("@/public/image/common/ico-back.png").default;


          2、webpack dev server

          webpack-dev-server是開發(fā)時的必備利器,它可以在本地起一個簡單的 web 服務(wù)器,當文件發(fā)生變化時,能夠?qū)崟r重新加載。webpack-dev-server的配置也很簡單:

          devServer: {
              contentBase: __projectDir, //頁面的基礎(chǔ)目錄
              publicPath:'/',
              port: 8080,
              host: '127.0.0.1',
              open: true, //是否運行后自動打開瀏覽器
              hot: true
          }

          啟動 webpack-dev-server 后,在目標文件夾中是看不到編譯后的文件的,實時編譯后的文件都保存到了內(nèi)存當中。

          1) HMR

          hot設(shè)置為 true 是啟用 webpack 的 模塊熱替換( HMR )功能,但這里注意必須要添加插件webpack.HotModuleReplacementPlugin 才能完全啟用 HMR

          2) publicPath

          publicPath 路徑下的打包文件可以在瀏覽器中訪問,webpack-dev-server 打包的內(nèi)容是放在內(nèi)存中的,并沒有實際創(chuàng)建文件,這些打包后的資源對外的的根目錄就是 publicPath。

          默認 devServer.publicPath 是 '/',所以你的包( bundle )可以通過 http://127.0.0.1:8080/bundle.js 訪問。注意:當這里的 publicPath 和 output 的 publicPath 同時設(shè)置時,這里的優(yōu)先級更高。


          總結(jié)

          webpack 的配置能介紹的點其實還有很多,例如開發(fā)環(huán)境和生產(chǎn)環(huán)境進行配置分離;利用瀏覽器的緩存將公共的模塊抽離分開打包;還有很多常用 plugins 插件等等。

          這篇文章是以我在開發(fā)某個多頁面應(yīng)用項目為例,總結(jié)了一些我在webpack配置上的理解。希望能對瀏覽這篇文章的小伙伴有幫助。



          作者:HZH

          來源-微信公眾號:三七互娛技術(shù)團隊

          出處:https://mp.weixin.qq.com/s/JzZDqe-f_NRMmdxDLXC7tQ

          篇我們主要介紹Webpack打包Javascript。當然,除了可以打包Javascript之外,webpack還可以打包html。但是這不是我們本篇的重點。我們可以參考 Webpack HTML 打包介紹——跡憶客

          現(xiàn)在讓我們擴展我們的項目——webpack-example,并為 entryoutput 屬性指定自定義名稱。 在 webpack.config.js 中,我們在 plugins 屬性之前添加以下內(nèi)容:

          entry: {
            main: path.resolve(__dirname, './src/app.js'),
          },
          output: {
            filename: '[name].bundle.js',
            path: path.resolve(__dirname, 'deploy')
          },

          完整代碼如下所示

          webpack.config.js 文件

          const HtmlWebpackPlugin = require("html-webpack-plugin");
          const path = require('path');
          
          module.exports = {
              entry: {
                  main: path.resolve(__dirname, './src/app.js'),
                },
                output: {
                  filename: '[name].bundle.js',
                  path: path.resolve(__dirname, 'deploy')
                },
              plugins: [
                  new HtmlWebpackPlugin({
                      hash: true,
                      title: 'Webpack - 跡憶客(jiyik.com)',
                  })
              ],
          };

          這里我們不使用 html 模板

          在這里,我們將入口文件更改為 app.js,并將輸出文件夾更改為 deploy 。 我們還稍微調(diào)整了生成的包文件的名稱。 現(xiàn)在它將以條目的名稱(“main”)開頭,后跟單詞“bundle”和 .js 文件擴展名。

          現(xiàn)在我們創(chuàng)建 src/component.js 文件:

          src/component.js

          export default (text = "Hello, Webpack!!") => {
            const element = document.createElement("h1");
          
            element.innerHTML = text;
          
            return element;
          };

          接下來,我們將現(xiàn)在項目中的 index.js 重命名為 app.js 以此反映我們的更改,并將其內(nèi)容替換為以下內(nèi)容:

          app.js

          import component from './component';
          
          document.body.appendChild(component());

          現(xiàn)在讓我們運行 webpack,看一下發(fā)生了什么

          $ npm run dev
          
          > webpack-example@1.0.0 dev /Users/jiyik/workspace/js/webpack-example
          > webpack --mode development
          
          asset main.bundle.js 4.33 KiB [emitted] (name: main)
          asset index.html 552 bytes [emitted] [compared for emit]
          runtime modules 670 bytes 3 modules
          cacheable modules 235 bytes
            ./src/app.js 77 bytes [built] [code generated]
            ./src/component.js 158 bytes [built] [code generated]
          webpack 5.54.0 compiled successfully in 142 ms

          運行之后我們會在項目目錄中看到生成了deploy文件夾,其中包含靜態(tài)html文件和js文件

          此時我們在瀏覽器中運行 deploy/index.html 文件,結(jié)果如下:

          此外,如果我們檢查 index.html 的源代碼,我們會看到 script 標簽中 src 屬性的值更新為 main.bundle.js

          此時,我們可以刪除 webpack 最初生成的 dist 文件夾,因為我們不再需要它了。


          將 ES6 轉(zhuǎn)換成 ES5

          接下來我們將了解如何將 ES6 轉(zhuǎn)換為適用于所有瀏覽器的 ES5 的代碼。 讓我們從運行以下命令開始:

          $ npm run dev -- --devtool inline-source-map

          在這里,我運行 webpack 并將 devtool 選項設(shè)置為 inline-source-map 以使代碼更具可讀性。 這樣可以更清楚地演示從 ES6 到 ES5 的代碼轉(zhuǎn)換。

          下面我們打開 main.bundle.js

          main.bundle.js 部分代碼

          /***/ "./src/component.js":
          /*!**************************!*\
            !*** ./src/component.js ***!
            \**************************/
          /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
          
          __webpack_require__.r(__webpack_exports__);
          /* harmony export */ __webpack_require__.d(__webpack_exports__, {
          /* harmony export */   "default": () => (__WEBPACK_DEFAULT_EXPORT__)
          /* harmony export */ });
          /* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ((text = "Hello, Webpack!") => {
              const element = document.createElement("h1");
            
              element.innerHTML = text;
            
              return element;
            });
          
          /***/ })
          
          /******/     });

          如您所見,來自 component.js 模塊的現(xiàn)代 ES6 特性(箭頭函數(shù)和 const 聲明)默認不會轉(zhuǎn)換為符合 ES5 的代碼。 為了讓我們的代碼在舊瀏覽器中工作,我們必須添加 Babel 加載器:

          $ npm install babel-loader @babel/core @babel/preset-env --save-dev

          然后在 webpack.config.js 文件中,在 output 項之后添加 module 項,如下所示

          module: {
            rules: [
              {
                test: /\.js$/,
                exclude: /node_modules/,
                use: {
                  loader: 'babel-loader',
                  options: {
                    presets: ['@babel/preset-env']
                  }
                }
              },
            ]
          },

          當我們?yōu)?webpack loader 定義規(guī)則時,通常需要定義三個主要屬性:

          • test - 它描述了應(yīng)該轉(zhuǎn)換什么樣的文件。
          • exclude - 它定義了不應(yīng)該從加載器處理的文件。
          • use - 它告訴應(yīng)該對匹配的模塊使用哪個加載器。 在這里,我們還可以設(shè)置加載器選項,就像我們剛剛完成的 presets 選項一樣。

          webpack.config.js

          const HtmlWebpackPlugin = require("html-webpack-plugin");
          const path = require('path');
          
          module.exports = {
              entry: {
                  main: path.resolve(__dirname, './src/app.js'),
              },
              output: {
                  filename: '[name].bundle.js',
                  path: path.resolve(__dirname, 'deploy')
              },
              module: {
                  rules: [
                    {
                      test: /\.js$/,
                      exclude: /node_modules/,
                      use: {
                        loader: 'babel-loader',
                        options: {
                          presets: ['@babel/preset-env']
                        }
                      }
                    },
                  ]
              },
              plugins: [
                  new HtmlWebpackPlugin({
                      title: 'Webpack - 跡憶客(jiyik.com)',
                  })
              ],
          };

          然后在運行 webpack 看會生成什么樣的文件

          $ npm run dev -- --devtool inline-source-map
          
          > webpack-example@1.0.0 dev /Users/liuhanzeng/workspace/js/webpack-example
          > webpack --mode development "--devtool" "inline-source-map"
          
          asset main.bundle.js 7.02 KiB [emitted] (name: main)
          asset index.html 257 bytes [compared for emit]
          runtime modules 670 bytes 3 modules
          cacheable modules 301 bytes
            ./src/app.js 76 bytes [built] [code generated]
            ./src/component.js 225 bytes [built] [code generated]
          webpack 5.54.0 compiled successfully in 1340 ms

          這次 main.bundle.js 中的代碼:

          /***/ "./src/component.js":
          /*!**************************!*\
            !*** ./src/component.js ***!
            \**************************/
          /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
          
          __webpack_require__.r(__webpack_exports__);
          /* harmony export */ __webpack_require__.d(__webpack_exports__, {
          /* harmony export */   "default": () => (__WEBPACK_DEFAULT_EXPORT__)
          /* harmony export */ });
          /* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (function () {
            var text = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : "Hello, Webpack!";
            var element = document.createElement("h1");
            element.innerHTML = text;
            return element;
          });
          
          /***/ })
          
          /******/     });

          非常完美。 現(xiàn)在我們可以使用現(xiàn)代 JS 功能(ES6),webpack 將轉(zhuǎn)換我們的代碼,以便它可以被舊瀏覽器執(zhí)行。

          天介紹 webpack 的一個最常用的插件:HTML Webpack Plugin。

          說它是使用 webpack 開發(fā)前端項目必不可少的插件也不為過,因為它可以自動幫我們將 webpack 打包生成的文件(比如 js 文件、css 文件)嵌入到 html 文件中。

          這在生成的文件帶有哈希串時尤為有用。

          在 webpack 配置文件引入 HtmlWebpackPlugin 插件,然后在 plugins 數(shù)組中通過 new HtmlWebpackPlugin() 加入 HtmlWebpackPlugin 實例對象即可。

          // webpack.config.js
          const HtmlWebpackPlugin = require('html-webpack-plugin');
          
          module.exports = {
            entry: './src/index.js',
            output: {
              filename: 'app.[contenthash:8].js',
            },
            mode: 'production',
            plugins: [
              new HtmlWebpackPlugin()
            ],
          }

          我們執(zhí)行 npx webpack 命令后,webpack 額外給我們生成了一個 dist/index.html 文件。該 html 文件格式化后得到的內(nèi)容為:

          <!doctype html>
          <html>
          <head>
            <meta charset="utf-8">
            <title>Webpack App</title>
            <meta name="viewport" content="width=device-width,initial-scale=1">
            <script defer="defer" src="app.c8b961ec13a790ae7d15.js"></script>
          </head>
          <body></body>
          </html>

          可以看到將打包好的 app.js 文件被自動嵌入到 head 元素下最后一個子元素位置。

          這里打包文件名尾部被添加了內(nèi)容哈希串,這意味著每次項目的內(nèi)容發(fā)生變化,哈希串的值都不同。

          試想下,如果你自己管理 html 文件,每次都要改這個 js 文件名,是要多累,還好有 HtmlWebpackPlugin 幫忙。

          當然前面這種只是 HtmlWebpackPlugin 插件的默認用法,我們可以做更具體的定制化。

          一些常用的屬性

          我們需要傳入一個配置對象來進行模板渲染定制化。

          HtmlWebpackPlugin 的配置非常豐富,不過常用的就幾個。

          plugins: [
            new HtmlWebpackPlugin({
              title: '前端西瓜哥的博客',
              favicon: 'static/favicon.ico',
            }),
          ],
          • titile:設(shè)置網(wǎng)頁標題;
          • filename:生成 html 文件名,默認值為 index/html
          • template:使用自己的模板,這里填這個模板的路徑,使用了之后一些配置項就無效了,比如 title;
          • favicon:指定網(wǎng)站圖標路徑,除了會在 html 上填充 favicon 相關(guān)內(nèi)容,還會將該文件拷貝到打包文件夾下,非常好用;
          • minify:是否壓縮 html 文件。不設(shè)置時,如果 webpack 的 mode 為 production,就會壓縮 html,移除多余的空格和注釋之類的。

          使用自定義 html 模板

          在實際開發(fā)中,通常是創(chuàng)建一個 index.html 提供給 HtmlWebpackPlugin 插件作為模板。

          這樣的話,title 等配置和一些更細碎的內(nèi)容就可以直接寫到 html 上。相比配置,直接在 html 上編輯要更直觀些。

          我們在根目錄創(chuàng)建一個 index.html 作為模板:

          <!DOCTYPE html>
          <html>
          <head>
            <meta charset="UTF-8">
            <meta name="viewport" content="width=device-width, initial-scale=1.0">
            <title>前端西瓜哥</title>
            <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
          </head>
          <body></body>
          </html>

          這樣就可以直接在 html 模板上添加 title,以及一些 cdn 形式的第三方庫。

          webpack.config.js 配置改為:

          plugins: [
            new HtmlWebpackPlugin({
              template: 'index.html'
            }),
          ],

          生成的 html 為:

          <!DOCTYPE html>
          <html>
          <head>
            <meta charset="UTF-8">
            <meta name="viewport" content="width=device-width, initial-scale=1.0">
            <title>前端西瓜哥</title>
            <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
            <script defer src="app.d02c9155f73c92f51bf5.js"></script>
          </head>
          <body></body>
          </html>

          第三方庫建議使用自己本地項目的,會更穩(wěn)定和安全些,比如上面就建議改為 <script src="static/jquery-3.6.0.min.js"></script>

          這里會用到一個 copy-webpack-plugin 插件將一些文件或文件夾拷貝到打包目錄下。關(guān)于這個插件我會另外專門寫一篇文章講解,這里不展開。

          自定義 html 注入變量

          webpack 支持通過使用 lodash.template() 的方式注入變量。

          我們將模板 html 改為下面這樣:

          <!DOCTYPE html>
          <html>
          <head>
            <meta charset="UTF-8">
            <meta name="viewport" content="width=device-width, initial-scale=1.0">
            <title><%= htmlWebpackPlugin.options.title %></title>
          </head>
          <body>
            <%= htmlWebpackPlugin.options.saySomething %>
          </body>
          </html>

          配置改為:

          plugins: [
            new HtmlWebpackPlugin({
              template: 'index.html',
              title: '前端西瓜哥的博客',
              // 下面這個是自定義屬性
              saySomething: 'Stay hungry, stay foolish'
            }),
          ],

          將傳入給 HtmlWebpackPlugin 的配置屬性會成為 htmlWebpackPlugin.options 對象下的屬性,嵌入到模板 html 下。

          所以這里的生成結(jié)果是:

          <!DOCTYPE html>
          <html>
          <head>
            <meta charset="UTF-8">
            <meta name="viewport" content="width=device-width, initial-scale=1.0">
            <title>前端西瓜哥的博客</title>
          <script defer src="app.d02c9155f73c92f51bf5.js"></script></head>
          <body>
            Stay hungry, stay foolish
          </body>
          </html>

          因為使用了 lodash.template 模板渲染豐富,除了可以嵌入變量的值,還支持判斷條件、循環(huán)等特性,基本上可以滿足我們的絕大多數(shù)場景

          結(jié)尾

          HTML Webpack Plugin 是被廣泛使用的 webpack 插件,用來將我們打包出來的文件自動嵌入到一個模板 HTML 中。

          實際開發(fā)中,通常我們會使用自己編寫的 html 模板。

          我是前端西瓜哥,熱衷于分享前端知識,歡迎關(guān)注我。


          主站蜘蛛池模板: 亚洲AV无码一区二三区| 精品视频一区在线观看| 3d动漫精品啪啪一区二区中| 久久精品国产一区二区三区| 丰满岳乱妇一区二区三区| 日本v片免费一区二区三区| 日韩精品一区二区三区色欲AV| 亚洲中文字幕无码一区| 亚洲无人区一区二区三区| 国产精品一区二区久久不卡| AV天堂午夜精品一区| 99国产精品一区二区| 国产视频一区二区在线播放| 九九久久99综合一区二区| 无码国产精品一区二区免费模式| 国产精品毛片一区二区三区| 无码人妻精品一区二区三| 精品国产aⅴ无码一区二区| 一区二区三区视频| 无码一区二区三区亚洲人妻| 国产午夜一区二区在线观看| 亚洲日本一区二区一本一道| 亚洲永久无码3D动漫一区| 日本午夜精品一区二区三区电影| 亚洲一区二区三区在线网站| 精品无人区一区二区三区在线| 天天综合色一区二区三区| 视频在线观看一区| 日本亚洲国产一区二区三区| 无码少妇一区二区性色AV | 国产成人一区二区三区| 波多野结衣一区二区三区高清在线 | 亚洲综合av永久无码精品一区二区| 国产精品视频一区二区三区四 | 国产AV国片精品一区二区| 日本一区二区在线| 中文字幕日韩欧美一区二区三区| 杨幂AV污网站在线一区二区| 亚洲爆乳精品无码一区二区三区| 中文字幕一区日韩精品| 无码人妻精品一区二区三区99不卡|