webpack打包文件常见问题

1.打包完成后引入文件的地址是哪个?

在打包输出的结果中: Entrypoint main = main.bundle.js

生成的其他代码文件会被入口函数添加到 header 的尾部。例如下例:
<script charset="utf-8" src="/Users/stone/Downloads/webpack4-learn-master/demo05/dist/vendors.15224047.js"></script>

2.代码分模块打包方法。

如下例子:

const path = require('path')
const CleanWebpackPlugin = require('clean-webpack-plugin')//会对打包信息进行清除。

module.exports = {
  entry: {
    main: './src/index.js'
  },
  output: {
    publicPath: __dirname + '/dist/', // js 引用的路径或者 CDN 地址
    path: path.resolve(__dirname, 'dist'), // 打包文件的输出目录
    filename: '[name].bundle.js', // 代码打包后的文件名
    chunkFilename: '[name].js' // 代码拆分后的文件名
  },
  optimization: {//对打包进行优化,用于大的模块进行分割
    splitChunks: {
      chunks: 'all',//共有三种,all,async,inital
      automaticNameDelimiter: '.', //名字分割的办法
      cacheGroups: {//希望的分割模式
        vendors: {//这个cache分组名一点用也没,只是个命名
          test: /[\\/]node_modules[\\/]/,//文件路径正则
          priority:-10,
          name: 'vendors',
          minSize: 40, //模块的最小分割大小
          maxSize: 50, //模块的最大分割大小,如果超过那个就可能会被分割。
        },
        lodash: {//对这个库单独打包
          minSize: 40,
          maxSize: 50,
          name: 'lodash',
          test: /[\\/]node_modules[\\/]lodash[\\/]/,
          priority: 5  // 优先级要大于 vendors 不然会被打包进 vendors
        },
        commons: {//也是自己定义的名字
          name: 'commons',
          minSize: 0, //表示在压缩前的最小模块大小,默认值是 30kb
          minChunks: 2, // 最小公用次数
          priority: 5, // 优先级
          reuseExistingChunk: true // 公共模块必开启
        },
        default: {//其他的默认情况下的分组方式
          minChunks: 2,
          priority: -20,
          reuseExistingChunk: true,
          name:"xxxxxx"//没有作用
        }
      }
    }
  },
  plugins: [new CleanWebpackPlugin()]
}

说明:主要用来对大的模块进行分解打包。
minSize:用来描述最小的分包大小。
maxSize:如果包超过了这个大小限制将会被再次分解,分解后的包是带了 hashid 的。同时他是尽可能的去分,如果不能分了,大小会存在超过限定的情况。

3.代码懒加载方法

document.addEventListener('click', function() {
  import(/* webpackChunkName: 'use-lodash'*/ 'lodash').then(function(_) {
    console.log(_.join(['3', '4']))
  })
})

懒加载的含义是,在触发某些事件的时候,才去动态的加载代码,然后执行代码。
上面的代码只有触发的时候,才会从后端将代码加载进来,然后执行对应的模块。

懒加载能加快网页的加载速度,如果你把详情页、弹窗等页面全部打包到一个 js 文件中,用户如果只是访问首页,只需要首页的代码,不需要其他页面的代码,加入多余的代码只会使加载时间变长,所以我们可以对路由进行懒加载,只有当用户访问到对应路由的时候,再去加载对应模块。

懒加载并不是 webpack 里的概念,而是 ES6 中的 import 语法,webpack 只是能够识别 import 语法,能进行代码分割而已。

可以使用浏览器的coverage 对代码的使用率进行分析。

页面刚加载的时候,异步的代码根本就不会执行,但是我们却把它下载下来,实际上就会浪费页面执行性能,webpack 就希望像这样交互的功能,应该把它放到一个异步加载的模块去写,例如下面的书写方法:
单独书写一个click.js文件

function handleClick() {
  const element = document.createElement('div')
  element.innerHTML = 'Dell Lee'
  document.body.appendChild(element)
}

export default handleClick

将 index.js 文件写为异步的加载模块:

document.addEventListener('click', () => {
  import('./click.js').then(({ default: func }) => {
    func()
  })
})

这么去写代码,才是使页面加载最快的一种方式,写高性能前端代码的时候,不光是考虑缓存,还要考虑代码使用率。

所以 webpack 在打包过程中,是希望我们多写这种异步的代码,才能提升网站的性能,这也是为什么 webpack 的 splitChunks 中的 chunks 默认是 async,异步的。

异步能提高你网页打开的性能,而同步代码是增加一个缓存,对性能的提升是非常有限的,因为缓存一般是第二次打开网页或者刷新页面的时候,缓存很有用,但是网页的性能一般是用户第一次打开网页,看首屏的时候。

当然,这也会出现另一个问题,就是当用户点击的时候,才去加载业务模块,如果业务模块比较大的时候,用户点击后并没有立马看到效果,而是要等待几秒,这样体验上也不好,怎么去解决这种问题?

4.代码懒加载方法用的太多也会变慢的---预加载方式

采用异步,如果用懒加载的方式,在用户点击触发事件后,才进行下载相关代码,然后执行相关代码,这个下载执行的过程可能也很消耗时间。

如果访问首页的时候,不需要加载详情页的逻辑,等用户首页加载完了以后,页面展示出来了,页面的带宽被释放出来了,网络空闲了,再「偷偷」的去加载详情页的内容,而不是等用户去点击的时候再去加载

这个解决方案就是依赖 webpack 的 Prefetching/Preloading 特性

document.addEventListener('click', () => {
  import(/* webpackPrefetch: true */ './click.js').then(({ default: func }) => {
    func()
  })
})

webpackPrefetch: true 会等你主要的 JS 都加载完了之后,网络带宽空闲的时候,它就会预先帮你加载好.

当网页打开的时候,main.bundle.js 被加载完了,网络空闲了,就会预先加载 1.js 耗时 14ms(注意文本格式Typetext/javascript,是个缓存),等我去点击页面的时候,Network 又多了一个 1.js(注意文本格式Typescript,是从缓存中读出来的),耗时 2ms,这是因为第一次加载完了 1.js,被浏览器给缓存起来了,等我点击的时候,浏览器直接从缓存中取,响应速度非常快。

这里我们使用的是 webpackPrefetch,还有一种是 webpackPreload

区别:
与 prefetch 相比,Preload 指令有很多不同之处:

Prefetch 会等待核心代码加载完之后,有空闲之后再去加载。Preload 会和核心的代码并行加载,还是不推荐

总结:
针对优化,不仅仅是局限于缓存,缓存能带来的代码性能提升是非常有限的,而是如何让代码的使用率最高,有一些交互后才用的代码,可以写到异步组件里面去,通过懒加载的形式,去把代码逻辑加载进来,这样会使得页面访问速度变的更快,如果你觉得懒加载会影响用户体验,可以使用 Prefetch 这种方式来预加载,不过在某些游览器不兼容,会有兼容性的问题,重点不是在 Prefetch 怎么去用,而是在做前端代码性能优化的时候,缓存不是最重要的点,最重要的是代码使用的覆盖率上(coverage)

5.解决因打包文件名称变动导致引入麻烦---html-webpack-plugin

经过上面几个小节的操作,有没有觉得每次要去更改 index.html 中引入 js 文件很麻烦,一旦打包的名字变更后,也要对应的去修改 index.html 引入的 js 名称,这个时候就要使用一个插件来帮助我们,打包完之后自动生成 HTML 文件,并自动引入打包后的 js 文件,安装的方法如下:

npm i html-webpack-plugin html-loader --save-dev

插件使用的配置方法如下:

module.exports = {
  plugins: [
    new HtmlWebpackPlugin({
      // 打包输出HTML
      title: '自动生成 HTML',
      minify: {
        // 压缩 HTML 文件
        removeComments: true, // 移除 HTML 中的注释
        collapseWhitespace: true, // 删除空白符与换行符
        minifyCSS: true // 压缩内联 css
      },
      filename: 'index.html', // 生成后的文件名
      template: 'index.html' // 根据此模版生成 HTML 文件
    })
  ]
}

title: 打包后生成 html 的 title
filename:打包后的 html 文件名称
template:模板文件(例子源码中根目录下的 index.html)
chunks:和 entry 配置中相匹配,支持多页面、多入口
minify:压缩选项
由于使用了 title 选项,则需要在 template 选项对应的 html 的 title 中加入 <%= htmlWebpackPlugin.options.title %>

然后入口函数就在 html 中就不需要引入了。

6.处理 css/scss 文件

CSS 在 HTML 中的常用引入方法有 <link> 标签和 <style> 标签两种,所以这次就是结合 webpack 特点实现以下功能:

  • 将 css 通过 link 标签引入
  • 将 css 放在 style 标签里

这次我们需要用到 css-loaderstyle-loader 等 loader,跟 babel 一样,webpack 不知道将 CSS 提取到文件中。需要使用 loader 来加载对应的文件

css-loader:负责解析 CSS 代码,主要是为了处理 CSS 中的依赖,例如 @importurl() 等引用外部文件的声明。

style-loader 会将 css-loader 解析的结果转变成 JS 代码,运行时动态插入 style 标签来让 CSS 代码生效。

安装的依赖

npm i css-loader style-loader --save-dev

同时需要增加如下的配置文件:

module: {
    rules: [
      {
        test: /\.css$/, // 针对 .css 后缀的文件设置 loader
        use: ['style-loader', 'css-loader']
      }
},

打包完成访问后发现样式表在 index 中内置了(可以发现是通过 <style> 标签注入的 css)。 这是因为:
style-loader, css-loader 两个 loader 的处理后,CSS 代码会转变为 JS,和 index.js 一起打包。

如果需要单独把 CSS 文件分离出来,我们需要使用 mini-css-extract-plugin 插件。

使用的配置方法如下:

module: {
    rules: [
      {
        test: /\.css$/, // 针对 .css 后缀的文件设置 loader
        use: [
          {//使用loader方式解析
            loader: MiniCssExtractPlugin.loader
          },
          'css-loader'
        ]
      }
    ]
  },
plugins: [
    new MiniCssExtractPlugin({
      filename: '[name].css',
      chunkFilename: '[id].css'
    })
  ]

说明:use 是对匹配的资源进从后到前的执行(这个顺序很诡异)。plugins字段相当于配置文件。使用上面的配置文件后,将会把 css 单独的打包到文件中。 但是这样的文件并没有压缩,需要,引入 optimize-css-assets-webpack-plugin 插件来实现 css 压缩

npm install optimize-css-assets-webpack-plugin --save-dev

在打包文件的 plugins 中加入如下的配置:

new OptimizeCssAssetsPlugin({
      assetNameRegExp: /\.css$/g,
      cssProcessor: require('cssnano'), //用于优化\最小化 CSS 的 CSS 处理器,默认为 cssnano
      cssProcessorOptions: { safe: true, discardComments: { removeAll: true } }, //传递给 cssProcessor 的选项,默认为{}
      canPrint: true //布尔值,指示插件是否可以将消息打印到控制台,默认为 true
    })

打开配置后发现 css 文件也已经被压缩了。

处理 scss 文件使用的依赖为

npm i node-sass sass-loader --save-dev

这里就不多介绍了。

注意!!!

module.rules.use 数组中,loader 的位置。根据 webpack 规则:放在最后的 loader 首先被执行,从上往下写的话是下面先执行从左往右写的话是右边先执行

['style-loader', 'css-loader', 'sass-loader']
执行顺序为 sass-loader --> css-loader --> style-loader
首先应该利用 sass-loader 将 scss 编译为 css,剩下的配置和处理 css 文件相同。
这个顺序确实是很诡异的!!!!!!!!!!

为 CSS 自动 加上浏览器前缀
安装 postcss-loader 和 autoprefixer 依赖

npm install postcss-loader autoprefixer --save-dev

7.JS Tree Shaking

字面意思是摇树,项目中没有使用的代码会在打包的时候丢掉。JS 的 Tree Shaking 依赖的是 ES6 的模块系统(比如:import 和 export)

这个在打包的时候已经被优化了。我们写代码的时候要求按需加载引入模块。

  1. 如何处理第三方 JS 库?
    对于经常使用的第三方库(例如 jQuery、lodash 等等),如何实现 Tree Shaking ?
    开头讲过,js tree shaking 利用的是 ES 的模块系统。而 lodash.js 使用的是 CommonJS 而不是 ES6 的写法。所以,安装对应的模块系统即可。
    安装 lodash.js 的 ES 写法的版本:npm install lodash-es --save

友情提示:
在一些对加载速度敏感的项目中使用第三方库,请注意库的写法是否符合 ES 模板系统规范,以方便 webpack 进行 tree shaking。

8.CSS Tree Shaking

能够将没有用到的 CSS 文件给清除掉。
使用的库如下:

npm i glob-all purify-css purifycss-webpack --save-dev

警告!!!
如果项目中有引入第三方 css 库的话,谨慎使用!!!

9.图片处理汇总

1.图片处理和 base64 编码
使用如下的依赖:
npm install url-loader file-loader --save-dev

在 webpack.config.js 中的 module.rules 选项中进行配置,以实现让 loader 识别图片后缀名,并且进行指定的处理操作。

rules: [
      {
        test: /\.(png|jpg|jpeg|gif)$/,
        use: [
          {
            loader: 'url-loader',
            options: {
              name: '[name]-[hash:5].min.[ext]',
              outputPath: 'images/', //输出到 images 文件夹
              limit: 20000 //把小于 20kb 的文件转成 Base64 的格式
            }
          }
        ]
      }
    ]

总结:
如果图片较多,会发很多 http 请求,会降低页面性能。
url-loader 会将引入的图片编码,转为 base64 字符串。再把这串字符打包到文件中,最终只需要引入这个文件就能访问图片了,节省了图片请求。

但是,如果图片较大,编码会消耗性能。因此 url-loader 提供了一个 limit 参数,小于 limit 字节的文件会被转为 base64,大于 limit 的使用 file-loader 进行处理,单独打包。

url-loader 依赖 file-loaderurl-loader 可以看作是增强版的 file-loader

图片压缩
图片压缩需要使用 img-loader 插件,除此之外,针对不同的图片类型,还要引用不同的插件。比如,我们项目中使用的是 png 图片,因此,需要引入 imagemin-pngquant,并且指定压缩率。压缩 jpg/jpeg 图片为 imagemin-mozjpeg 插件

10.字体处理

对字体的处理和对 css 的样式极像。

11.处理第三方 JS 库

如何使用和管理第三方 JS 库?

项目做大之后,开发者会更多专注在业务逻辑上,其他方面则尽力使用第三方 JS 库来实现。
由于 js 变化实在太快,所以出现了多种引入和管理第三方库的方法,常用的有 3 中:
1.CDN:<script></script> 标签引入即可 2.npm 包管理:目前最常用和最推荐的方法 3.本地 js 文件:一些库由于历史原因,没有提供 ES6 版本,需要手动下载,放入项目目录中,再手动引入。

针对第三种方法,如果没有 webpack,则需要手动引入 import 或者 require 来加载文件;但是,webpack 提供了 alias 的配置,配合 webpack.ProvidePlugin 这款插件,可以跳过手动入,直接使用!

关于我
loading