webpack 7 days

130 阅读5分钟

webpack

一 解决了什么问题

1.1 模块演进

  • 1 文件划分
    • /index.css
      /index.html
      /index.js

    • 缺点

        1. 全局污染
        1. 没有私有空间
        1. 容易产生命名冲突
        1. 无法管理依赖关系
        1. 不易维护
    • 完全依靠约定,非常不可靠

  • 2 命名空间
    • moduleA.method1()
      moduleB.method2()

    • 只解决了命名冲突问题

  • 3 IIFE
    • 2 基础上增加了立即执行韩式
      •   ;(function($){  
              var name = 'module-a'  
              function method1(){  
                  //...  
              }  
              window.moduleA={  
                  method1:method1  
              }  
          })  
        
    • 解决模块依赖关系问题<br>通过参数表明依赖
  • 4 模块加载问题依然没解决

1.2 模块化规范的出现

  • 1 一个统一的模块化标准规范
  • 2 一个可自动加载模块的基础库
  • 3 CommonJS 
    • nodeJS
      • 同步模式
  • 4 AMD
    • 浏览器
      • 异步模块

二 如何使用webpack

2.1 一个ES Module例子

  • src/heading.js

    export default ()=>{  
      const element = document.createElement('h2')  
      element.addEventListener('click', ()=> alert('Hello webpack'))  
      return element;  
    }  
    
  • src/index.js

    import createHeading from './heading.js'  
    const heading = createHeading()  
    document.body.append(heading)  
    
  • index.html

    <script type="module" src="src/index.js"/>   
    
    • type="module" 为ES module 约定方式

2.2 webpack

  • npx webpack
    • 默认从src/index.js开始打包
      • index.html<br><script src="dist/main.js" />

2.3 配置文件

  • webpack.config.js
    module.exports={  
      entry: './src/main.js',  
      output: {  
        filename: 'bundle.js',  
        path: path.join(__dirmane, 'output')  
      }  
    }  
    

2.4 webpack智能提示

  • webpack.config.js

    //import {Configuration} from 'webpack'  
      
    /**  
    
    * @type {Configuration}  
    */  
    const config = {  
      entry: './src/main.js',  
      output: {  
        filename: 'bundle.js',  
        path: path.join(__dirmane, 'output')  
      }  
    }  
      
    module.exports= config  
    
    • import {Configuration} from 'webpack'
      • 此行是为VS code导入的,运行webpack时要记得注释掉

2.5 针对不同环境的预设配置

  • production
    • 启动内置优化,自动优化打包结果,速度慢
  • development
    • 优化打包速度,添加调试辅助插件
  • none
    • 原始打包,不做任何额外处理
  • 修改工作模式配置 
    • 通过CLI --mode参数传入
    • 通过配置文件设置mode属性
      • module.exports={<br>  mode: 'none'<br>}

三 Loader

负责各种资源模块加载

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

四 plugin

4.1 清除dist目录

  •   const {CleanWebpackPlugin} = require('clean-webpack-plugin')  
      module.exports={  
        plugins: [  
          new CleanWebpackPlugin(),  
        ]  
      }  
    

4.2 打包时生成HTML文件且引入bundle.js

  •   const HtmlWebpackPlugin = require('html-webpack-plugin')  
      module.exports={  
        plugins: [  
          new HtmlWebpackPlugin({  
            title: 'Webpack Plugin Sample',  
            meta: {  
              viewport: 'width=device-width'  
            }  
          })  
        ]  
      }  
    
  • 如需定制更多,可创建模版文件

  • 可生成多个html文件,通常配合多入口配置

4.3 将不参与构建的文件copy到dist目录

  • 如favicon/robots.txt文件, public ,static 目录
  •   const CopyWebpackPlugin = require('copy-webpack-plugin')  
      module.exports={  
        plugins: [  
          new CopyWebpackPlugin([  
            'public',  
          ]),  
        ]  
      }  
    

五 提高生产力

5.1 草根方式

  • 启动HTTP服务
  • webpack自动打包
    • --watch
  • Source Map 定位错误
  • 监听文件变化, 自动刷新
    • npm i browser-sync --global
      browser-sync dist --watch

      或者也可使用npx直接使用远端模块
      npx browser-sync dist --watch

5.2 webpack-dev-server

  • 结合 --watch 和 broker-sync

  • npm i webpack-dev-server --save-dev
    // or
    npx webpack-dev-server

  •   web pack.config.js  
      const path = require(‘path’)  
        
      module.exports={  
        //...  
        devServer: {  
          contentBase: path.join(__dirname, ‘dist’),  
          compress: true,  
          port: 9000  
          // ...  
        }  
      }  
    
    • 详细配置:webpack.js.org/configuration/dev-server
  • 静态资源访问
    默认将构建结果全部作为开发服务器的资源文件,只要通过webpack打包能够输出的文件都可直接被试问到。

    但如果还有没有参与打包的静态文件也需要作为开发服务器的资源被访问,那就需要额外的配置

    •   webpack.config.js  
        module.exports={  
          //...  
          devServer:{  
            contentBase: ‘public’  
          }  
        }  
      
  • porxy代理
    在实际生产环境中能直接访问的API
    回到开发环境后
    再次访问这些API会产生跨越请求问题

    • 解决方案
      在开发服务器中配置一个后端API的代理服务
      也就是把后端接口服务代理到本地的开发服务地址

      •   webpack.config.js  
          module.exports={  
            //...  
            devServer:{  
              proxy: {  
                ‘/api’: {  
                   target: ‘https://api.github.com’  
                }  
              }  
            }  
          }  
        
      • 通常我们希望去掉/api
        可添加一个pathRewrite来实现重写

        •   webpack.config.js  
            module.exports={  
              //...  
              devServer:{  
                proxy: {  
                  ‘/api’: {  
                     target: ‘https://api.github.com’,  
                     pathRewrite: {  
                        ‘^/api’: ‘’  // 正则规则  
                     },  
                     changeOrigin: true    
                     // 确保请求的主机名就是api.github.com  
                  }  
                }  
              }  
            }  
          
  • HMR模块热替换

    • 运行webpack-dev-server命令时,--hot参数开启

    • 配置文件中
      1.devServer中的hot设置为true
      2.插件HotModuleReplacementPlugin

      • webpack.config.js
        module.exports={  
          devServer:{  
            hot:true  
          },  
          plugins:[  
            new webpack.HotModuleReplacementPlugin()  
          ]  
        }  
        
    • 1.CSS通过loader已实现了热更新
      2.vue-cli 或 create-react-app 实现了框架代码的热更新
      3.JS文件需手动处理

      • main.js
        import createEditor from ‘./editor’  
        import log from ‘./icon.pngimport ‘./global.cssconst editor=createEditor()  
        document.body.appendChild(editor)  
          
        let lastEditor=editor;  
        module.hot.accept(‘./editor’, ()=>{  
          //当./editor.js更新,自动执行此函数  
          //临时记录更新前编辑器内容  
          const value=lastEditor.innerHTML  
          //移除更新前的元素  
          document.body.removeChild(lastEditor)  
          //创建新的编辑器  
          //此时createEditor已经是更新后的函数  
          lastEditor = createEditor()  
          //还原编辑器内容  
          lastEditor.innerHTML=value  
          //追加到页面  
          document.body.appendChild(lastEditor)  
        })   
        

5.3 SourceMap 最佳实践

  • 简单模式
    • web pack.config.js
      module.exports = {  
        devtool: ’source-map’  
      }  
      
  • cheap-module-eval-source-map
    • 开发环境建议
      1.需要调试转换前代码
      2.定位到行即可
      3.虽然启动慢,但开发时都是使用webpack-dev-server在watch模式下重新打包,它重新打包速度非常快
  • none
    • 生产环境建议
      1.保护源代码
      2.调试应是开发环境的问题
  • 不是webpack特有的功能
    两者关系是webpack支持SourceMap

六 优化

6.1 Tree Shaking

  • 生产模式自动开启
  • 只支持 ES module,babel-loader 默认根据环境决定是否转换
  • webpack.config.js
    module.exports={  
      optimization:{  
        // 模块只导出被使用的成员  
        usedExports: true,  
        // 压缩输出结果  
        minimize: true,  
        // 尽可能合并每一个模块到一个函数中  
        concatenateModules: true  
      }  
    }  
    

6.2 sideEffects

  • 模块执行时出了导出成员是否还做了其他事情
    开发npm模块时会用到
    完整移除没用到的模块

  • webpack.config.js

    module.exports={  
        optimization:{  
            sideEffects: true  
        }  
    }  
    

6.3 生产环境下 分包

Code Splitting

  • 按需加载,降低启动成本,提高响应速度
  • 多入口打包
    •   output:{filename:’[name].bundle.js’},  
        plugins:[  
        {  
            new HtmlWebpackPlugin({  
                title:’Multi Entry’,  
                template: ‘./src/index.html’,  
                filename: ‘index.html’,  
                chunks: [‘index’]  // 指定使用index.bundle.js  
            }),  
            new HtmlWebpackPlugin({  
                title: ‘Multi Entry’,  
                template: ‘./src/album.html’,  
                filename: ‘album.html’,  
                chunks: [‘album’]  // 指定使用album.bundle.js  
            })  
        }  
        ]  
      
    • 不同入口中一定会存在公共使用的模块
      自动提取公共模块

      •   optimization: {  
              splitChunks:{ chunks: ‘all’ }  
          }  
        
  • 按需加载
    • 在应用运行工程中,需要某个资源模块时,才去加载这个模块
      •   import(‘./posts/posts’).then(({default:posts})=>{  
              mainElement.appendChild(posts())  
          })  
          // 魔法注释,指定分包名  
          import(/*webpackChunkName:’posts’*/‘./album/album’).then(({default:album})=>{  
              mainElement.appendChild(album())  
          })  
        

6.4 不同环境不同配置

  • 中小项目
    •   module.exports=(env,argv)=>{  
            config = { /*不同环境下公共配置*/ }  
          
            if(env === ‘development’){  
                config.mode = ‘development’  
                config.devtool = ‘cheap-eval-module-source-map’  
            }else if(env === ‘production’){  
                config.mode = ‘production’  
                config.devtool = ‘nosources-source-map’  
            }  
            return config  
        }  
      
      • env通过cli传递的不同环境名参数
      • argv运行cli工程中的所有参数
  • 大型项目
    • webpck.common.js...................公共配置
      webpack.dev.js.........................开发环境配置
      webpack.prod.js.......................生产环境配置

      • webpack.common.js

        module.exports={  
            // 公共配置  
        }  
        

        webpck.prod.js

        const merge = require(‘webpack-merge’)  
        const common = require(‘./webpack.common’)  
        module.exports = merge(common, {  
            // 生产环境配置  
        })  
        

        webpck.dev.js

        const merge = require(‘webpack-merge’)  
        const common = require(‘./webpack.common’)  
        module.exports = merge(common, {  
            // 开发环境配置  
        })  
        
    • 配置完成后,通过--config参数来指定配置文件

      • webpck --config webpack.prod.js

6.6 Define Plugin

  • webpck.config.js

    module.exports = {  
      plugins: [  
        new webpack.DefinePlugin({  
            // 值要求是一个代码片段  
            API_BASE_URL: JSON.stringify(‘https://api.example.com’)  
         )  
      ]  
    }  
    
  •   console.log(API_BASE_URL)  
      // https://api.example.com  
    

6.7 mini-css-extract-plugin

  • 提取css代码到.css文件
  •   module:{  
        rules: [  
          {  
            test: /\.css$/,  
            use: [  
               // style-loader 将样式通过style标签注入  
              MiniCssExtractPlugin.loader,  
              ‘css-loader’  
            ]  
          }  
        ]  
      },  
      optimization: {  
          minimizer: [  
             // 开启minimizer时,默认的JS压缩会关闭,需手动调用JS压缩  
             new TerserWebpackPlugin(),  
             // 压缩css配置在此次,只在开启minimizer特性时工作  
             new OptimizeCssAssetsWebpackPlugin()  
          ]  
      },  
      plugins:[  
         new MiniCssExtractPlugin(),  
         // 压缩CSS配置在此次, 任何情况使用  
         new OptimizeCssAssetsWebpackPlugin()  
        
      ]