學(xué)習(xí)筆記(十)——Webpack打包

Webpack打包

webpack是一款目前主流的模塊化打包工具,提供了對前端開發(fā)過程中涉及的所有資源的模塊化打包方案

模塊化打包工具由來

  • 解決開發(fā)階段代碼在實際生產(chǎn)運行環(huán)境中的兼容性問題
image-20200917221904481
  • 將零散的模塊文件打包到統(tǒng)一的文件中,避免由于模塊文件過多造成頻繁的網(wǎng)絡(luò)請求
image-20200917221921877
  • 實現(xiàn)所有前端資源的模塊化,而不僅僅是JS模塊化
image-20200917221942279

快速上手(基本使用)

  • 在項目目錄中,執(zhí)行yarn add webpack webpack-cli --dev安裝webpack依賴
  • 執(zhí)行yarn webpack命令,對項目目錄中的文件進行打包,打包后的文件默認輸出到dist目錄

配置文件及基礎(chǔ)配置

  • webpack4以上版本支持按照約定零配置直接打包,以src/index.js為入口文件 -> 以dist/main.js為輸出文件

  • 使用webpack.config.js配置文件可以對webpack進行自定義打包配置

    • entry:指定入口文件
    • output:指定輸出文件
      • filename:輸出文件名
      • path:輸出文件路徑(必須為絕對路徑,可以使用path.join(__dirname, 'output')獲取絕對路徑)
      • publicPath:資源目錄,默認值為空字符串
    • mode:指定工作模式
    • module:
      • rules:配置loader加載規(guī)則,一個對象數(shù)組
        • test:正則表達式匹配使用loader的文件
        • use:指定使用的loader,可以接收loader名字符串(或指定loader文件路徑)、配置對象,或者由多個loader名字符串、配置對象組成的數(shù)組,數(shù)組中配置的多個loader會按照從后往前的順序依次執(zhí)行
          • loader:loader名
          • options:配置選項,用于指定loader的配置參數(shù)
    • plugins:配置插件,接收一個數(shù)組
    // webpack.config.js
    const path = require('path');
    
    module.exports = {
        mode: 'none',
        entry: './src/style.css',
        output: {
            filename: 'bundle.js',
            path: path.join(__dirname, 'output')
        },
        module: {
            rules: [
                {
                    test: /.css$/,
                    use: ['style-loader', 'css-loader']
                }
            ]
        },
        plugins: [
    
        ]
    }
    

工作模式

webpakc可以通過命令行參數(shù)--mode=指定工作模式,默認不指定時執(zhí)行production模式,也可以在配置文件中通過mode配置項進行指定

  • production
    • 生產(chǎn)模式,會啟用內(nèi)置的插件對打包的代碼進行壓縮優(yōu)化等操作
  • development
    • 開發(fā)模式,更注重打包效率,不對代碼進行壓縮
  • none
    • 純打包模式,以最原始的狀態(tài)打包代碼

打包結(jié)果運行原理

以none模式打包代碼,查看打包后的輸出文件,并進行分析

  • 入口函數(shù)

    • 定義了一個對象來緩存加載的模塊 installedModules

      image-20200921203134994
    • 定義了一個函數(shù)__webpack_require__來加載模塊,返回exports

      image-20200921203154670
    • 在定義的__webpack_require__函數(shù)上掛載了一些數(shù)據(jù)與工具函數(shù)

      image-20200921203223450
    • 在最后調(diào)用__webpack_require__函數(shù)來加載入口模塊,并返回exports

      image-20200921203237447
  • 入函數(shù)參數(shù)傳入的是一個由模塊函數(shù)組成的數(shù)組,每個模塊被包裹在一個相同結(jié)構(gòu)的函數(shù)當中 function(module, __webpack_exports__, __webpack_require__){},用來形成模塊私有作用域

    image-20200921203940524
  • 每個模塊函數(shù)中都會執(zhí)行__webpack_require__.r(__webpack_exports__),用來為exports對象定義一個__esModule標記

    image-20200921204053586
image-20200921204313513
  • 模塊中使用__webpack_require__加載其他依賴的模塊,并執(zhí)行模塊中相應(yīng)的代碼

資源模塊加載

webpack使用loader實現(xiàn)對模塊的加載,默認loader只對js文件進行加載與解析,對于css等其他資源模塊的加載,則可以通過配置額外的loader來實現(xiàn)

loader是webpack實現(xiàn)前端模塊的核心,通過不同的loader可以實現(xiàn)加載任何類型的資源

image-20200921205035572
  • 在webpack.confg.js中,通過module屬性,配置loader加載規(guī)則

  • module

    • rules:配置loader加載規(guī)則,一個對象數(shù)組
      • test:正則表達式匹配使用loader的文件
      • use:指定使用的loader,可以接收loader名字符串,或者由多個loader名字符串組成的數(shù)組,數(shù)組中配置的多個loader會按照從后往前的順序依次執(zhí)行
    module: {
        rules: [
            {
                test: /.css$/,
                use: ['style-loader', 'css-loader']
            }
        ]
    }
    

導(dǎo)入資源模塊

通常情況下,應(yīng)當使用js文件作為模塊打包的入口,其他資源文件通過import的方式引入

webpack建議應(yīng)當在對應(yīng)的代碼模塊中引入當前模塊所需要的資源(按需加載),而不是在全局入口引入

image-20200921210706180

常用資源加載器-loader

  • file-loader:文件資源加載器、

    image-20200921220222982
  • url-loader:類似于file-loader,區(qū)別是當文件小于指定大?。赏ㄟ^options下的limit選項配置)時,可以直接返回DataURL,將圖片等轉(zhuǎn)換成base64編碼直接存放在js文件中,將小文件通過這種方式處理,可以減少頁面加載時,瀏覽器請求資源文件的次數(shù),提高加載效率

    • url-loader依賴于file-loader,無法單獨使用

    • 配置樣例

       {
           test: /.png$/,
           use: {
               loader: 'url-loader',
                   options: {
                       limit: 10 * 1024 // 10KB
                   }
           }
       }
      
  • css-loader:編譯css文件到j(luò)s中,以style形式掛載到html中

    image-20200921220207106
  • eslint-loader:語法格式校驗

    image-20200921220316275
  • babel-loader:ES語法轉(zhuǎn)換

  • html-loader:加載處理html文件

資源加載方式

  • 遵循ES Modules標準的import聲明
  • 遵循CommonJS標準的require聲明
  • 遵循AMD標準的define函數(shù)和require函數(shù)
  • css文件中的@import與css屬性中使用的url(例如background-image)
  • html中的src、a標簽的href等

核心工作原理

  • 以指定js文件為入口,查找資源文件依賴,并生成依賴樹
  • 遍歷依賴樹,將依賴的資源模塊交給相應(yīng)的loader進行處理
  • 將最終處理的結(jié)果匯總到輸出的bundle.js文件中
image-20200921221837689

開發(fā)一個loader

嘗試開發(fā)一個markdown文件加載器markdown-loader

  • 創(chuàng)建markdown-loader.js文件,使用module.exports導(dǎo)出一個處理函數(shù),接收加載的模塊內(nèi)容作為參數(shù)

  • loader返回的是經(jīng)過處理過后的結(jié)果,最后執(zhí)行的loader,必須返回js腳本

    image-20200921225002907
  • 直接返回js腳本方式

    // markdown-loader.js
    const marked = require('marked');
    
    module.exports = source => {
        const html = marked(source)
    
        return `module.exports = ${JSON.stringify(html)}`
    }
    
  • 使用html-loader來進行后續(xù)處理方式

    // markdown-loader.js
    const marked = require('marked');
    
    module.exports = source => {
        const html = marked(source)
    
        // return `module.exports = ${JSON.stringify(html)}`
        return html
    }
    
    {
        test: /.md$/,
            use: [
                'html-loader',
                './markdown-loader.js'
            ]
    }
    

插件及常用插件

loader用來處理資源打包,plugin則用來處理除了打包以外的其他自動化工作,例如:清除指定目錄、拷貝資源文件、壓縮輸出代碼等

通過webpack.config.js中配置plugins屬性來配置plugin

webpack loader + plugin實現(xiàn)了前端工程化大部分工作

  • clean-webpack-plugin:清除指定目錄,默認清理輸出目錄

    const { CleanWebpackPlugin } = require('clean-webpack-plugin');
    module.exports = {
        ...
        plugins: [
            new CleanWebpackPlugin()
        ]
    }
    
  • html-webpack-plugin:自動生成html文件

    • filename:指定生成html文件名,默認index.html

    • title:html的title

    • meta:html的meta

    • template:指定生成html的模板文件,模板文件中的動態(tài)參數(shù)可以使用<%= htmlWebpackPlugin.options.title %>方式指定

    • 配置多個htmlWebpackPlugin實例,可以生成多個html文件

      const HtmlWebpackPlugin = require('html-webpack-plugin');
      
      module.exports = {
          ...
          plugins: [
              new HtmlWebpackPlugin({
                  title: 'hello world',
                  meta: {
                      viewport: ''
                  },
                  template: 'src/index.html'
              }),
              new HtmlWebpackPlugin({
                  filename: 'about.html'
              })
          ]
      }
      
  • copy-webpack-plugin:拷貝指定文件到輸出目錄,接收一個路徑數(shù)組

    const CopyWebpackPlugin = require('copy-webpack-plugin');
    
    module.exports = {
        ...
        plugins: [
            // 開發(fā)階段不建議使用,頻繁的拷貝讀寫磁盤沒有意義,同時會降低打包效率
            new CopyWebpackPlugin({
                patterns: [
                    {
                        from: 'public'
                    }
                ]
            }),
        ]
    }
    

開發(fā)一個插件

webpack的插件通過hook機制實現(xiàn)

webpack規(guī)定一個插件必須是一個函數(shù),或者一個包含apply方法的對象,apply方法接收一個compiler對象參數(shù)

  • 嘗試實現(xiàn)一個去除js文件注釋的插件

    class MyPlugin {
        apply(compiler) {
            compiler.hooks.emit.tap('MyPlugin', compilation => {
                for (const name in compilation.assets) {
                    if(name.endsWith('.js')) {
                        const contents = compilation.assets[name].source()
                        const cleanContents = contents.replace(/\/\*\*+\*\//g, '')
                        compilation.assets[name] = {
                            source: () => cleanContents,
                            size: ()=> cleanContents.length,
                        }
                    }
                }
            })
        }
    }
    
    module.exports = {
        ...
        plugins: [
            new MyPlugin(),
        ]
    }
    

使用webpack提升開發(fā)體驗

  • 自動編譯

    • 使用--watch參數(shù)
  • 自動刷新瀏覽器

    • 可以使用browser-sync工具來實現(xiàn)瀏覽器自動刷新,缺點是對于webpack來說需要額外的工具來實現(xiàn),且需要webpack在監(jiān)聽到文件改動后頻繁的寫入磁盤,更好的方式是使用webpack-dev-server插件
  • Dev Server

    webpack-dev-server提供了一個本地開發(fā)服務(wù)器,同時集成了自動編譯、自動刷新瀏覽器等功能

    打包編譯的結(jié)果存放在內(nèi)存中,由集成的http server直接讀取,避免了頻繁的磁盤讀寫,提高了編譯效率

    • 通過配置文件中的devServer屬性,可以對webpack-dev-server進行相應(yīng)的參數(shù)配置

      • contentBase:指定額外靜態(tài)資源目錄

      • proxy:代理配置

            devServer: {
                contentBase: 'public',
                proxy: {
                    '/api': {
                        // http://localhost:8080/api/users -> https://api.github.com/api/users
                        target: 'https://api.github.com',
                        // http://localhost:8080/api/users -> https://api.github.com/users
                        pathRewrite: {
                            '^/api': '', // 正則匹配替換路徑
                        },
                        // 以實際代理請求的主機名訪問,localhost對于外部服務(wù)器無法識別
                        changeOrigin: true,
                    }
                }
            }
        
  • Source Map

    Source Map用來幫助開發(fā)者調(diào)試壓縮后的js、css文件,便于進行問題定位

    在壓縮后的代碼中,通過在最后添加指定格式的注釋,來開啟Souce Map的引用

    例如 //# sourceMappingURL=xxxx.map,xxxx.map指代對應(yīng)的Source Map文件名

    • 通過配置文件中的devtool屬性devtool: 'source-map'來配置在webpack中開啟Source Map

    • webpack中提供了多種不同模式的Source Map,不同的模式在打包速度、打包質(zhì)量上存在一定的差異

      image-20200923231232477
      • eval模式:將模塊函數(shù)放到eval中執(zhí)行,不生成source map文件,只能定位到代碼來自哪個文件,構(gòu)建速度較快
      • eval-source-map模式:將模塊還數(shù)放到eval中執(zhí)行,生成source map文件,可以定位原始代碼位置,構(gòu)建速度最慢
      • cheap-eval-source-map模式:定位到的代碼文件經(jīng)過loader轉(zhuǎn)換,只能定位到行
      • cheap-module-eval-source-map模式:定位到的代碼文件是轉(zhuǎn)換前的源碼,只能定位到行,構(gòu)建速度慢
      • hidden-source-map模式:生成source map,但不在代碼中引入,通常適合工具庫開發(fā)使用
      • nosources-source-map模式:可以定位到代碼位置,但是無法查看源碼,在生產(chǎn)模式下使用,保護源碼
    • 模式組合

      • eval:是否使用eval執(zhí)行代碼模塊
      • cheap:source map是否包含行信息
      • module:是否能得到loader處理前的源碼
    • 生產(chǎn)模式下,不建議生成source map,容易暴露源碼

  • 自動刷新與HMR

    自動刷新功能當監(jiān)視到代碼資源文件發(fā)生改動,會自動重新編譯打包,并刷新瀏覽器,此時會導(dǎo)致瀏覽器上正在編輯的內(nèi)容以及操作狀態(tài)會丟失,這樣在實際開發(fā)過程中的體驗并不是很好,更理想的方式是在瀏覽器不刷新的情況下實現(xiàn)代碼資源改動的實時替換,即HMR(Hot Module Replacement)的功能,HMR極大的提升了開發(fā)效率

    • webpack-dev-server集成了HMR功能,有兩種開啟方式
      • 運行參數(shù)增加--hot
      • 配置屬性devServer下增加hot: true,同時配置啟用webpack.HotModuleReplacementPlugin插件
    • 開啟webpack的HMR后,css樣式就實現(xiàn)了自動熱更新,而js、圖片資源等需要開發(fā)者使用HMR API module.hot.accept() 去手動處理熱更新,通常使用一些前端框架或者腳手架工具創(chuàng)建的前端項目,都已經(jīng)集成了成熟完整的HRM處理方案
    • 如果HMR實現(xiàn)上存在異常,webpack會退而使用自動刷新頁面來完成更新,通過使用hotOnly: true 配置,可以只使用HMR而不會使用自動刷新功能
  • 生產(chǎn)環(huán)境優(yōu)化

    生產(chǎn)環(huán)境打包與開發(fā)環(huán)境打包通常存在較大的差異,webpack4為不同的模式提供了預(yù)設(shè)的配置,也推薦開發(fā)者為不同模式提供不同的配置文件,例如將通用配置放入webpack.common.js中,為開發(fā)環(huán)境提供webpack.dev.js,為生產(chǎn)環(huán)境提供webpack.prod.js,在對應(yīng)模式的配置文件中,使用webpack-merge插件引入并合并配置,并在執(zhí)行打包命令時使用--config參數(shù)指定要使用的配置文件,例如webpack --config webpack.prod.js

    • DefinePlugin:用于為代碼注入全局成員,例如process.env.NODE_ENV,這是webpack內(nèi)置的插件,可以在配置文件plugins中進行配置

    • Tree Shaking:去除未引用代碼(dead-code)、去除冗余代碼,Tree-Shaking并不是一項單獨的配置,是一系列優(yōu)化功能組合使用的結(jié)果,production生產(chǎn)模式下自動開啟

      • 在配置文件中使用optimization屬性集中配置優(yōu)化選項
      • usedExports: true 只導(dǎo)出外部使用的成員
      • minimize: true 壓縮代碼
      • concatenateModules: true 盡可能將模塊代碼合并到一個函數(shù)中輸出(默認一個模塊對應(yīng)一個函數(shù)),這樣既提升了代碼執(zhí)行效率,又減少了代碼的體積,這種方式又被稱為Scope Hoisting(作用域提升,webpack3中添加的特性)
      • sideEffects: true:一般用于npm包標記副作用,在package.json中配置sideEffects屬性來指定哪些代碼包含副作用
    • Tree Shaking的實現(xiàn)基于ES Module,當使用了Babel插件或者babel-loader來處理轉(zhuǎn)換es代碼時,可能會將ES Module轉(zhuǎn)換為CommonJS模塊,此時Tree Shaking將不生效,最新的babel-loader中自動禁用了ES Module的轉(zhuǎn)換,Tree Shaking可以生效

    • Code Splitting - 代碼分割

      隨著前端工程越來越復(fù)雜,打包形成的bundle.js文件會變的越來越龐大,而并不是所有模塊都需要在應(yīng)用啟動時使用,如果將所有模塊打包到一個文件中,會使得網(wǎng)絡(luò)加載js時間較久。降低應(yīng)用加載啟動的速度(首頁白屏?xí)r間)

      • 多入口打包:適合多頁應(yīng)用,每個頁面作為一個打包入口,公共部分單獨提取

        // 多入口配置樣例
        const path = require('path');
        const { CleanWebpackPlugin } = require('clean-webpack-plugin');
        const HtmlWebpackPlugin = require('html-webpack-plugin');
        
        module.exports = {
            mode: 'none',
            entry: { //使用對象而不是數(shù)組,每個屬性代表一個入口
                index: './src/index.js',
                hello: './src/hello.js',
            },
            output: {
                filename: '[name].bundle.js',
                path: path.join(__dirname, 'dist'),
            },
            module: {
                rules: [
                    {
                        test: /.css$/,
                        use: ['style-loader', 'css-loader']
                    },
                ]
            },
            plugins: [
                new CleanWebpackPlugin(),
                new HtmlWebpackPlugin({
                    filename: 'index.html',
                    template: 'src/index.html',
                    chunks: ['index'],
                }),
                new HtmlWebpackPlugin({
                    filename: 'hello.html',
                    template: 'src/hello.html',
                    chunks: ['hello'],
                }),
            ],
            optimization: {
                splitChunks: {
                    chunks: 'all' //提取公共js、css到一個單獨的bundle
                }
            }
        
        }
        
      • 動態(tài)導(dǎo)入:按需加載,使用ES Module中的import()函數(shù)來實現(xiàn);動態(tài)導(dǎo)入的模塊會被自動分包,無需額外配置,適合單頁應(yīng)用對路由組件的按需加載

        • 在import()函數(shù)中使用指定注釋可以用來指定打包的bundle文件名稱,例如import(/*webpackChunkName: utils*/ './utils')
        • MiniCssExtractPlugin:用來實現(xiàn)css文件的按需加載,使用這個插件的時候,css會被提取到一個單獨的文件,可以直接通過link標簽引入,而不是style標簽,因此不需要再使用style-loader,而轉(zhuǎn)為使用MiniCssExtractPlugin.loader;當css文件的大小并不是很大的時候,意義不是很大
        • OptimizeCssAssetsWebpackPlugin:壓縮css樣式文件,單獨的css文件不會默認被webpack壓縮,需要借助額外的插件
          • 壓縮的插件默認不應(yīng)該放在plugins中,因為開發(fā)階段并不需要對js與css進行壓縮,應(yīng)當將相應(yīng)的插件放入optimization屬性的minimizer配置中,這樣當以production生產(chǎn)模式進行打包時,就會壓縮相應(yīng)的資源,而開發(fā)模式不會進行壓縮
    • Hash文件名:輸出文件名使用占位符實現(xiàn)輸出帶hash值的文件名,有三種hash模式

      • [hash]:全局hash,每次打包hash值都會發(fā)生變化
      • [chunkhash]:相同chunk使用相同的hash值,每次修改,只有對應(yīng)發(fā)生修改的chunk的hash會發(fā)生變化
      • [contenthash]:不同的文件會有獨立的hash值,每次修改,只有對應(yīng)文件發(fā)生修改的hash會發(fā)生變化

      在占位符中使用冒號+數(shù)值,可以指定hash位數(shù),使用[contenthash: 8] 8位contenthahs來控制緩存在實際應(yīng)用中較為合適

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容