webpack开发、使用及优化总结

参考open in new window

构建流程

  • 初始化参数:从配置文件和 Shell 语句中读取与合并参数,得出最终的参数;
  • 开始编译:用上一步得到的参数初始化 Compiler 对象,加载所有配置的插件和生命周期,执行对象的 run 方法开始执行编译;
  • 确定入口:根据配置中的 entry 找出所有的入口文件
  • 编译模块:从入口文件出发,调用所有配置的 Loader 对模块进行翻译(转化为AST树进行处理),再找出该模块依赖的模块,再递归本步骤直到所有入口依赖的文件都经过了本步骤的处理;
  • 完成模块编译:在经过第4步使用 Loader 翻译完所有模块后,得到了每个模块被翻译后的最终内容以及它们之间的依赖关系;
  • 输出资源:根据入口和模块之间的依赖关系,组装成一个个包含多个模块的 Chunk,再把每个 Chunk 转换成一个单独的文件加入到输出列表,这步是可以修改输出内容的最后机会;
  • 输出完成:在确定好输出内容后,根据配置确定输出的路径和文件名,把文件内容写入到文件系统。

Hash&ChunkHash&ContentHash的区别

  • hash:根据构建目录生成,只要项目中有文件修改,则整个项目构建出来的hash都会改变
  • chunkhash:和webpack打包到chunk有关,不同entry会生成不同的ChunkHush。chunk之间互不影响。(一般用来打包公用js)
  • contenthash:根据文件内容生成hash,只要文件内容不变,生成的ContentHash就不变(一般用来打包css)

webpack是解决什么问题而生的?

webpack 为前端的工程化开发提供了一套相对容易和完整的解决方案

tree-shaking使用

  • 使用ES6模块
  • Babel默认会将任何模块类型都转译成CommonJS类型;所以需要在.babelrc文件中或在webpack.config.js文件中设置modules: false
  • 使用UglifyJsPlugin插件

如何打包,到浏览器如何解包

利用webpack如何优化前端性能?

webpack的plugins的实现原理

  • Webpack 启动后,在读取配置的过程中会先执行 new myPlugin(options) 初始化一个 myPlugin 获得其实例。
  • 在初始化 compiler 对象后,再调用 myPlugin.apply(compiler) 给插件实例传入 compiler 对象。
  • 插件实例在获取到 compiler 对象后,就可以通过 hooks(webpack 把编译过程中触发的各类关键事件封装成事件接口的钩子)监听到 Webpack 发布出来的事件。 并且可以通过 compiler 对象去操作 Webpack。

webpack的loaders的实现原理

每个 Webpack 的 Loader 都需要导出一个函数,这个函数就是我们这个 Loader 对资源的处理过程,它的输入就是加载到的资源文件内容,输出就是我们加工后的结果。我们通过 source 参数接收输入,通过返回值输出

code split 介绍

为了减少HTTP请求,通常我们会讲所有代码打包到一个文件中,但是如果文件过大,也会影响页面的加载速度,尤其是会增加白屏时间,所以我们不妨将代码分成一块一块的;并且只在需要的时候去加载它;还可以利用浏览器将这些模块缓存起来;一般情况下分离有两种

  • 将第三方代码从业务代码中抽离(verdor)
  • 根据不同入口的业务代码按需加载(利用 import()语法)

webpack优化

webpack优化的瓶颈主要有两个方面:构建过程耗时打包的体积太大;

针对构建时间的优化(从依赖分析、依赖编译、处理输出三个方向):

  • 开发环境下启动hard-source-webpack-plugin,通过缓存编译过程优化启动速度
  • loader处理结果缓存
    1. babel-loader缓存,通过cacheDirectory开启缓存
      webpack.module.rules:{
        test: /\.js$/,
        use: [
          {
            loader: resolve('babel-loader'),
            options: {
              babelrc: false,
              // 将 babel 编译过的模块缓存在 webpack_cache 目录下,下次优先复用
              cacheDirectory: true,
            },
          },
        ]
      }
      
    2. eslint-loader缓存,通过cache选项指定缓存路径;注:配置更后如果不起作用则需要将cache文件删除
      webpack.module.rules:{
        test: /\.(js|vue)$/,
        enforce: 'pre',
        use: [
          {
            options: {
              // 启动缓存
              cache: true,
            },
            loader: require.resolve('eslint-loader'),
          },
        ]
      }
      
    3. css/scss缓存通过cache-loader辅助构建结果缓存
  • 缩小编译范围,减少不必要的检索与递归遍历、编译工作
    1. 指定模块解析路径,加速模块查找
      webpack.resolve: {  // 指定以下目录寻找第三方模块,避免webpack往父级目录递归搜索
        modules: {
          'node_modules', 
          path.resolve(projectDir, 'src'), 
          path.resolve(projectDir, 'node_modules')
        }
      }
      
    2. 忽略未用模块化的库,不去解析它们以提升编译速度
      webpack.module: {
        noParse: /node_modules\/(jQuery|chart\.js)/,
      }
      
    3. 通过alias配置别名, 减小查找过程
        webpack.resolve: { 
          alias: {
            react: path.resolve(__dirname, './node_modules/react/dist/react.min.js')
          }
        }
    
    1. 为loader添加include/exclude缩小babel的编译范围
    webpack.module.rules: {
      test: /\.js$/,
        use: [{
          loader: 'babel-loader',
          query: {
            // 将 babel 编译过的模块缓存在 webpack_cache 目录下,下次优先复用
            cacheDirectory: './webpack_cache/',
          },
        }],
        // 减少 babel 编译范围,忘记设置会让 webpack 编译慢上好几倍
        include: path.resolve(__dirname, 'src'),
    }
    
  • 通过uglifyjs-webpack-plugin插件开启多个子线程压缩代码
  • 通过Happypack开启多线程加速loader对文件的转换速度;使用 HappyPack 也有一些限制,它只兼容部分主流的 loaderopen in new window;webpack4已经弃用,推荐用thread-loader
    module.exports = {
      module: {
        rules: [
          {
            test: /\.js?$/,
            use: ['happypack/loader?id=babel'],
            include: path.resolve(__dirname, 'src'),
          },
        ]
      },
      plugins: [
        new HappyPack({
          id: 'babel',
          loaders: [{
            loader: 'babel-loader',
            query: {  
              cacheDirectory: './webpack_cache/',
            },
          }],
        })
      ],
    };
    
  • 使用thread-loader放置在其他 loader 之前;放置在这个 loader 之后的 loader 就会在一个单独的 worker【worker pool】 池里运行;请仅在耗时的 loader 上使用文档open in new window
module.exports = {
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        // 创建一个 js worker 池
        use: [ 
          'thread-loader',
          'babel-loader'
        ] 
      },
      {
        test: /\.s?css$/,
        exclude: /node_modules/,
        // 创建一个 css worker 池
        use: [
          'style-loader',
          'thread-loader',
          'css-loader'
          },
          'postcss-loader'
        ]
      }
    ]
  }
}

针对打包体积的优化

  • DllPluginDllReferencePlugin提前打包公共模块;注:不建议将所有公共模块放在dll中,否则无法用webpack的代码分割按需加载功能
  • 使用ModuleConcatenationPlugin插件开启scope-hoisting(作用域提升)功能,尽可能的把打包出来的模块合并到一个函数中去(代码需要符合es6的module规范)

    注意: webpack4中,去掉了这个插件改为optimization.concatenateModules配置,默认在生产环境是开启的

  • 使用 ES6 模块来开启 tree shaking,css通过purgecss-webpack-plugin插件进行tree-shanking优化
  • 按照路由拆分代码,实现按需加载
  • 使用webpack-bundle-analyzer对打包文件进行分析,将大文件模块提取出来在html中单独加载
    webpack配置:
    // ...
    externals: {
        'element-ui': 'Element',
        'v-charts': 'VCharts'
    }
    
    html:
    <script src="https://unpkg.com/element-ui@2.10.0/lib/index.js"></script>
    

webpack 热更新原理

  • 开启热更新后会启动一个HMR Server服务,同时在打包文件中注入HMR Runtime(用来启动websocket,来保持和server的通信,通过修改entry配置注入)
// 修改后的entry入口
{ entry:
  { index: 
    [
      // 添加websocket代码
      'xxx/node_modules/webpack-dev-server/client/index.js?http://localhost:8080',
      // 检查更新逻辑
      'xxx/node_modules/webpack/hot/dev-server.js',
      // 自己的入口
      './src/index.js'
  ],
  },
} 
  • 热更新插件会在webpack的compiler.hooks.done钩子中注册监听编译完成事件,当修改了一个或多个文件是,webpack会重新编译,编译完成后触发钩子
setupHooks() {
  const {done} = compiler.hooks;
  // 监听webpack的done钩子,tapable提供的监听方法
  done.tap('webpack-dev-server', (stats) => {
      this._sendStats(this.sockets, this.getStats(stats));
      this._stats = stats;
  });
};
  • server服务会在编译完成后通过websoket想浏览器发送hashok事件
// 通过websoket给客户端发消息
_sendStats() {
  this.sockWrite(sockets, 'hash', stats.hash); // 发送最新hash
  this.sockWrite(sockets, 'ok'); // 通知刷新
}
  • 浏览器利用hash,发送xxx/${hash}.hot-update.json请求,获取下次更新的hash和更新模块
{
  "h": "0c256052432b51ed32c8", // 下次更新hash
  "c": {
    "302": true // 更新的模块,如果没有则不需要更新
  }
}
  • 同时浏览器利用hash,发送xxx/${hash}.hot-update.js请求,利用jsop方式返回更新模块代码,jsonp的方式对返回的代码可以直接执行

参考open in new window参考open in new window参考open in new window

webpack环境变量

  • package.json中设置,为了兼容性需要用cross-env包
"scripts": {
    "build": "cross-env NODE_ENV=production webpack --config webpack.config.js",
    "dev": "cross-env NODE_ENV=development webpack-dev-server"
}
  • 在命令行中设置
webpack --env.production   // 设置 env.production == true
webpack --env.platform=web  // 设置 env.platform == "web"

注意,这种方式需要对webpack配置的module.exports原来指向对象的方式改为函数

module.exports = env => {
  console.log('NODE_ENV: ', env.NODE_ENV) // 'local'
  console.log('platform: ', env.platform) // web

  return {
    entry: './src/index.js',
    output: {
      filename: 'bundle.js',
      path: path.resolve(__dirname, 'dist')
    }
  }
}
  • 通过DefinePlugin配置
const env = {
   BASE_API:'"https://www.xxxx.com"' // 其它一些参数配置
}
plugins: [
    new webpack.DefinePlugin({
      'process.env': env,
      'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV)
    })
]
  • 配置文件mode
module.exports = {
    // 定义环境变量
    mode: 'development',
    // JavaScript 执行入口文件
    entry: './main.js',
    output: {
        // 把所有依赖的模块合并输出到一个 bundle.js 文件
        filename: 'bundle.js',
        // 输出文件都放到 dist 目录下
        path: path.resolve(__dirname, './dist'),
    }, 
};
// 或者执行时设置
webpack --mode=production

注意: 设置modewebpack内部会自动执行DefinePlugin设置变量open in new window
注意:npm脚本里的设置多用于配置相关;而DefinePlugin和mode选项定义的NODE_ENV 作用于webpack入口文件下的业务代码

gulp和webpack的区别

  • gulp强调的是工作流;通过配置一系列task,顺序执行来构建工作流程
  • webpack是一个前端模块化解决方案,侧重于模块化打包,将一切(图片、文件)都视为模块;通过loader(加载器)和plugins(插件)对资源进行处理
小红包免费领
小礼物走一走
Last Updated:
Contributors: slbyml
部分内容来源于网络,如有侵权,请留言或联系994917123@qq.com;访问量:waiting;访客数:waiting