谈谈webpack

简介: 构建工具有很多,比如Npm Script任务执行者、Grunt也是任务执行者、Gulp基于流的自动化构建工具、Fis3百度构建工具、Webpack打包模块化JavaScript工具和Rollup模块打包工具。

构建工具有很多,比如Npm Script任务执行者、Grunt也是任务执行者、Gulp基于流的自动化构建工具、Fis3百度构建工具、Webpack打包模块化JavaScript工具和Rollup模块打包工具。


这里谈谈用得很频繁的webpack构建工具。


基本概念


从最基本的概念开始了解:


入口(entry)


entry是配置模块的入口,必填。


module.exports = {
  entry: './path/to/my/entry/file.js'
}
复制代码


出口(output)


output配置如何输出最终想要的代码。output是一个object,里面包含一系列的配置项。


output.filename配置输出文件的名称,为string类型。


output.path配置输出文件存放在本地的目录(路径),必须是string类型的绝对路径。


path: path.resolve(__dirname, 'dist_[hash]')
复制代码


output.publicPath配置发布到线上资源的URL前缀,为string类型。默认为空字符串'',即使用相对路径。


比如需要将构建的资源上传到CDN服务上,以便加快网页的打开速度。配置代码如下:


filename: '[name]_[chunkhash:8].js'
publicPath: 'https://cdn.example.com/assets/'
复制代码


发布到线上时候,HTML中引入的JavaScript文件如下:


<script src='https://cdn.example.com/assets/a_12345678.js'></script>
复制代码


线上出现404错误的时候,看下路径有没有错~


还有其他配置请看文档


模块(module)


module配置如何处理模块。


module.rules配置模块的读取和解析规则,通常用来配置Loader。其类型是一个数组,数组里每一项都描述了如何去处理部分文件。应用一项rules时大致通过以下方式:


  1. 条件匹配:通过testincludeexclude三个配置项来命中Loader要应用规则的文件。


  1. 应用规则:对选中后的文件通过use配置项来应用Loader,可以只应用一个Loader或者按照从后往前的顺序应用一组Loader,同时还可以给Loader传入参数。


  1. 重置顺序:一组Loader执行顺序默认是从右往左执行,通过enforce选项可以让其中一个Loader的执行顺序放在前面或者最后。


module: {
  rules: [
    {
      // 命中scss文件
      test: /\.scss$/,
      // 处理顺序从右往左
      use: ['style-loader', 'css-loader', 'sass-loader'],
      // 排除node_modules目录下的文件
      exclude: path.resolve(__dirname, 'node_modules'),
    }
  ]
}
复制代码


Loader需要传入多个参数的时候的例子:


use: [
  {
    loader:'babel-loader',
    options:{
      cacheDirectory:true,
    },
    // enforce:'post' 的含义是把该 Loader 的执行顺序放到最后
    // enforce 的值还可以是 pre,代表把 Loader 的执行顺序放到最前面
    enforce:'post'
  },
  // 省略其它 Loader
]
复制代码


module.noParse配置项可以让webpack忽略对部分没采用模块化的文件的递归解析和处理,这样做有助于提高构建性能。比如:


module: {
  noParse: (content) => /jquery|lodash/.test(content)
}
复制代码


module.rules.parser属性可以更细粒度的配置哪些模块需要解析,哪些不需要,和noParse配置项的区别在于parser可以精确到语法层面,而noParse只能控制哪些文件不被解析。


module: {
  rules: [
    {
      test: /\.js$/,
      use: ['babel-loader'],
      parser: {
        amd: false, // 禁用 AMD
            commonjs: false, // 禁用 CommonJS
            ...
      }
    }
  ]
}
复制代码


解析(resolve)


Resolve配置webpack如何寻找模块所对应的文件。Webpack内置Javascript模块化语法解析功能,默认会采用模块化标准里面约定好的规则去寻找,你也可以按照需求修改默认规则。


resolve.alias配置项通过别名来把原导入的路径映射成一个新的导入路径。如下:


resolve: {
  alias: {
    components: './src/components/'
  }
}
复制代码


当你通过import Button from 'components/button'导入时,实际上被alias等价替换了import Button from './src/components/button'


resolve.modules配置webpack去哪些目录下找第三方模块,默认只会去node_modules目录下寻找。


resolve.enforceExtension如果配置为true所有导入语句都必须带有后缀,例如开启前import './foo能正常工作,开启后就必须写成import './foo.js'


插件(plugin)


Plugin用于扩展Webpack功能,各种各样的Plugin几乎让Webpack可以做任何构建相关的事情。


举个例子:


const CommonsChunkPlugin = require('webpack/lib/optimize/CommonsChunkPlugin');
module.exports = {
  plugins: [
    // 所有页面都会用到的公共代码提取到 common 代码块中
    new CommonsChunkPlugin({
      name: 'common',
      chunks: ['a', 'b']
    })
  ]
}
复制代码


DevServer配置


在开发环境的时候使用。要配置DevServer,除了在配置文件里面通过devServer传入参数外,还可以通过命令行参数传入。


注意:只有在通过DevServer去启动Webpack时配置项文件里devServer才会生效。


devServer.hot配置是否启用使用DevServer中提到的模块热替换功能。


devServer.host配置项用于配置 DevServer 服务监听的地址。


devServer.port配置项用于配置 DevServer 服务监听的端口,默认使用8080端口。


devServer.https配置HTTPS协议服务。某些情况下你必须使用HTTPS,HTTP2 和 Service Worker 就必须运行在 HTTPS 之上。


devServer: {
  https: true
}
复制代码


webpack原理


Webpack的运行是一个串行的过程,从启动到结束会执行以下流程:


  1. 初始化参数:从配置文件和Shell语句中读取与合并参数,得到最终的参数


  1. 开始编译:用上一步得到的参数初始化Compiler对象,加载所有配置的插件,执行对象的run方法开始执行编译。


  1. 确定入口:根据entry找出所有文件


  1. 编译模块:从入口文件出发,调用所有配置的Loader对模块进行编译,再找到模块依赖的模块,再递归本步骤,直到所有入口依赖的文件都经过了本步骤的处理;


  1. 完成编译:在第四步骤后,得到了每个模块被编译的内容和它们直接的依赖关系;


  1. 输出资源:根据入口和模块之间的依赖关系,组装成一个个包含多个模块的chunk,再把每个chunk转换成一个单独的文件加入到输出内容后,这一步是可以修改输出内容的最后机会。


  1. 输出完成:在确定好输出内容后,根据配置确定输出的路径和文件名,把文件内容写入系统。


webpack优化


缩小文件搜索范围


  • 优化loader配置


Loader对文件的转换操纵很耗时,需要让尽可能少的文件被Loader处理。


在使用Loader时可以通过testincludeexclude三个配置项来命中Loader要应用规则的文件。


module.exports = {
  module: {
    rules: [
      {
        test: /\.js$/,
        use: ['babel-loader?cacheDirectory'],
        include: path.resolve(__dirname, 'src')
      }
    ]
  }
}
复制代码


  • 优化resolve.modules配置


resolve.modules用于配置webpack去哪些目录下寻找第三方模块。


resolve.modules的默认值是['node_modules'],含义是先去当前的目录下./node_modules目录下去找想找的模块,以此类推,如果没有找到就去上一级目录../node_modules中找,再没有去上上一级,以此类推...


如果知道安装的模块在项目的根目录下的./node_modules时候,没有必要按照默认的方式一层层找:


module.exports = {
  resolve: {
    modules: [path.resolve(__dirname, 'node_modules')]
  }
}
复制代码


  • 优化resolve.alias配置


resolve.alias配置项通过别名来把原导入路径映射成一个新的导入路径。可以减少耗时的递归解析操作。


  • 优化module.noParse配置


module.noParse配置项可以让Webpack忽略对部分没采用模块化的文件的递归解析处理,这样做的好处是能提高构建性能。


const path = require('path');
module.exports = {
  module: {
    // 独完整的 `react.min.js` 文件就没有采用模块化,忽略对 `react.min.js` 文件的递归解析处理
    noParse: [/react\.min\.js$/],
  }
}
复制代码


把任务分解为多个子进程去并发执行


HappyPack把任务分解成多个子进程并发执行,子进程处理完后再把结果发送给主进程。减少了总的构建时间。


const HappyPack = require('happypack');
const os = require('os');
const happyThreadPool = HappyPack.ThreadPool({ size: os.cpus().length });
module.exports = {
  module: {
    rules: [
      {
        test: /\.js$/,
        loader: 'happypack/loader?id=happyBabel',
        exclude: /node_modules/
      }
    ]
  },
  plugins: [
    new HappyPack({
      id: 'happyBabel',
      loaders: [{
        loader: 'babel-loader?cacheDirectory= true',
      }],
      // 共享进程池
      threadPool: happyThreadPool,
      // 允许happypack输出日志
      verbose: true,
    })
  ]
}
复制代码


使用自动刷新


例如:


module.export = {
  watch: true,
  watchOptions: {
    // 监听到变化发生后会等300ms再去执行动作,防止文件更新太快导致重新编译频率太高
      // 默认为 300ms
      aggregateTimeout: 300,
      // 判断文件是否发生变化是通过不停的去询问系统指定文件有没有变化实现的
      // 默认每隔1000毫秒询问一次
      poll: 1000
  }
}
复制代码


由于保存文件的路径和最后编辑时间需要占用内存,定时检查周期检查需要占用CPU以及文件I/O,所以最好减少需要监听的文件数量和降低检查频率。


热替换


热替换就是当一个源码发生改变的时,只重新编译发生改变的模块,再用新输出的模块替换掉浏览器中对应的老模块。


开启热替换:


webpack-dev-server --hot
复制代码


区分环境


区分开发环境和生产环境,进行不同的构建~


CDN加速


CDN又叫内容分发网络,通过把资源部署到世界各地,用户在访问时按照就近原则从离用户最近的服务器获取资源,从而加速资源的获取速度。


CDN 其实是通过优化物理链路层传输过程中的网速有限、丢包等问题来提升网速的。


结合publicPath来处理:


const path = require('path');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const { WebPlugin } = require('web-webpack-plugin');
module.exports = {
  output: {
    filename: '[name]_[chunkhash:8].js',
    path: path.resolve(__dirname, './dist'),
    publicPath: '//js.cdn.com/id/'
  },
  module: {
    rules: [
      {
        test: /\.css$/,
        use: ExtractTextPlugin.extract({
              use: ['css-loader?minimize'],
              publicPath: '//img.cdn.com/id/'
          }),
      }
    ]
  },
  plugins: [
    new WebPlugin({
        template: './template.html',
        filename: 'index.html',
        // 指定存放 CSS 文件的 CDN 目录 URL
        stylePublicPath: '//css.cdn.com/id/',
      }),
      new ExtractTextPlugin({
        // 给输出的 CSS 文件名称加上 Hash 值
        filename: `[name]_[contenthash:8].css`,
      }),
  ]
}
复制代码


tree shaking优化


将多余的代码移除。


webpack --display-used-exports --optimize-minimize
复制代码


提取公共代码


公共代码的提取。


const CommonsChunkPlugin = require('webpack/lib/optimize/CommonsChunkPlugin');
new CommonsChunkPlugin({
  // 从哪些 Chunk 中提取
  chunks: ['a', 'b'],
  // 提取出的公共部分形成一个新的 Chunk,这个新 Chunk 的名称
  name: 'common'
})
复制代码


按需加载


对于采用单页应用作为前端架构的网站来说,会面临一个网页需要加载的代码量很大的问题,因为许多功能都做到了一个HTML里面,这会导致网页加载缓慢、交互卡顿、用户体验将非常糟糕。


导致这个问题的根本原因在于一次性的加载所有功能对应的代码,但其实用户每一阶段只可能使用其中一部分功能。 所以解决以上问题的方法就是用户当前需要用什么功能就只加载这个功能对应的代码,也就是所谓的按需加载。


Webpack 内置了强大的分割代码的功能去实现按需加载。比如:


  • 网页首次加载时只加载main.js文件,网页会展示一个按钮main.js文件中只包含监听按钮事件和加载按需加载的代码。
  • 当按钮被点击时才去加载被分割出去的show.js文件,加载成功后再执行show.js里的函数。


main.js中:


window.document.getElementById('btn').addEventListener('click', function () {
  // 当按钮被点击后才去加载 show.js 文件,文件加载成功后执行文件导出的函数
  import(/* webpackChunkName: "show" */ './show').then((show) => {
    show('Webpack');
  })
});
复制代码


show.js中:


module.exports = function (content) {
  window.alert('Hello ' + content);
};
复制代码


代码中最关键的一句是import(/* webpackChunkName: "show" */ './show'),Webpack 内置了对import(*)语句的支持,当 Webpack 遇到了类似的语句时会这样处理:


  • ./show.js为入口新生成一个Chunk
  • 当代码执行到import所在语句时才会去加载由Chunk对应生成的文件。
  • import返回一个Promise,当文件加载成功时可以在Promisethen方法中获取到show.js导出的内容。


在工作中具体使用到的时候再按需要进行更改配置项啦~


相关文章
|
5天前
|
数据采集 人工智能 自然语言处理
3分钟采集134篇AI文章!深度解析如何通过云无影AgentBay实现25倍并发 + LlamaIndex智能推荐
结合阿里云无影 AgentBay 云端并发采集与 LlamaIndex 智能分析,3分钟高效抓取134篇 AI Agent 文章,实现 AI 推荐、智能问答与知识沉淀,打造从数据获取到价值提炼的完整闭环。
390 93
|
6天前
|
域名解析 人工智能
【实操攻略】手把手教学,免费领取.CN域名
即日起至2025年12月31日,购买万小智AI建站或云·企业官网,每单可免费领1个.CN域名首年!跟我了解领取攻略吧~
|
5天前
|
SQL 人工智能 自然语言处理
Geo优化SOP标准化:于磊老师的“人性化Geo”体系如何助力企业获客提效46%
随着生成式AI的普及,Geo优化(Generative Engine Optimization)已成为企业获客的新战场。然而,缺乏标准化流程(Geo优化sop)导致优化效果参差不齐。本文将深入探讨Geo专家于磊老师提出的“人性化Geo”优化体系,并展示Geo优化sop标准化如何帮助企业实现获客效率提升46%的惊人效果,为企业在AI时代构建稳定的流量护城河。
394 156
Geo优化SOP标准化:于磊老师的“人性化Geo”体系如何助力企业获客提效46%
|
5天前
|
数据采集 缓存 数据可视化
Android 无侵入式数据采集:从手动埋点到字节码插桩的演进之路
本文深入探讨Android无侵入式埋点技术,通过AOP与字节码插桩(如ASM)实现数据采集自动化,彻底解耦业务代码与埋点逻辑。涵盖页面浏览、点击事件自动追踪及注解驱动的半自动化方案,提升数据质量与研发效率,助力团队迈向高效、稳定的智能化埋点体系。(238字)
278 158
|
13天前
|
机器人 API 调度
基于 DMS Dify+Notebook+Airflow 实现 Agent 的一站式开发
本文提出“DMS Dify + Notebook + Airflow”三位一体架构,解决 Dify 在代码执行与定时调度上的局限。通过 Notebook 扩展 Python 环境,Airflow实现任务调度,构建可扩展、可运维的企业级智能 Agent 系统,提升大模型应用的工程化能力。