webpack初体验

210 阅读5分钟

初始webpack

webpack是一个打包模块化js的工具,可以通过loader转换文件,通过plugin扩展功能。

webapck会递归地构建一个依赖关系图(dependency graph),其中包含应用程序需要的每个模块,然后将所有这些模块打包成一个或多个 bundle。

大致的有四个核心概念:

  • 入口(entry)
  • 输出(output)
  • loader
  • 插件(plugins)

本文以webpack4为例,带你认识认识他们。

entry 入口

    {
        // 多入口打包。 app1, app2代表着后面的[name]. 后面以app1单文件入口为例
        entry: {
            app1: path.resove(__dirname, './app1.js'),
            app2: path.resove(__dirname, './app2.js'),
            
        }
    }

output 输出

```js
{   
    <!--
    path:文件打包输出的绝对路径位置。
    filename: 文件名字。
    publicPath: 输出解析文件的目录,url 相对于 HTML 页面(html的script标签url: './static/app1.ad212311c12.js')
    下面的配置输出如下: dist<static<app1.ad212311c12.js
    -->
    output: {
        path: pat.resove(__dirname, './dist'),
        filename: path.posix.join('static', "[name].[hash].js"),
        publicPath: './'
    }
}
```

注:path.posixpath.win32,前者跨平台,后者只是win上

resolve

webpack在启动后会从配置的入口模块触发找出所有依赖的模块,resolve配置webpack如何寻找模块对应的文件

```js
<!--
    alias: 配置项通过别名来把原来导入路径映射成一个新的导入路径.
    extensions: 在导入语句没带文件后缀时,webpack会自动带上后缀去尝试访问文件是否存在.
-->
 {
    resolve: {
        alias: {
          ' @': path.resolve(__dirname, '../src')
        },
        extensions: ['.js', '.vue', '.json']
    }
 }
```

loader

loader是一种打包的方案,webpack默认只识别js结尾的文件,不同的loader转化不同的模块。

  • babel-loader(针对JS文件)
  • style-loader(针对CSS文件)
  • css-loader(针对CSS文件)
  • postcss-loader(针对CSS文件)
  • stylus-loader(针对CSS文件)
  • sass-loader(针对CSS文件)
  • url-loader(针对FILE文件)
  • file-loader(针对FILE文件)
  • vue-loader(针对vue文件)

babel-loader: 转换es5语法。需配合babel-core使用.

版本:babel-loader@7.x.x对应babel-core@6.x.x,babel-loader@8.x.x对应babel-core@7.x.x. 如:"babel-core": "^6.22.1"。"babel-loader": "^7.1.1",

Babel执行编译的过程中,会从项目的根目录下的 .babelrc文件中读取配置。.babelrc是一个json格式的文件

理解 babel-polyfill 和 babel-runtime 及 babel-plugin-transform-runtime

  • 语法转义器。主要对javascript最新的语法糖进行编译,并不负责转译javascript新增的api和全局对象。例如let/const就可以被编译,而includes/Object.assign等并不能被编译。常用到的转译器包有,babel-preset-env、babel-preset-es2015、babel-preset-es2016、babel-preset-es2017、babel-preset-latest等。在实际开发中可以只选用babel-preset-env来代替余下的,但是还需要配上javascirpt的制作规范一起使用,同时也是官方推荐。
  • 主要负责转译javascript新增的api和全局对象,例如babel-plugin-transform-runtime这个插件能够编译Object.assign,同时也可以引入babel-polyfill进一步对includes这类用法保证在浏览器的兼容性
  • jsx和flow插件,这类转译器用来转译JSX语法和移除类型声明的,使用Rect的时候你将用到它,转译器名称为babel-preset-react

npm install -D babel-preset-stage-2 babel-preset-env babel-polyfill babel-plugin-transform-runtime

// .bebelrc
{
  "plugins": [
    [
      "transform-runtime",
      {
        "polyfill": false
      }
    ]
  ],
  "presets": [
    [
      "env",
      {
        "modules": false
      }
    ],
    "stage-2"
  ]
}

{
    module: {
       rules: [
        {
            test: /\.js$/,
            loader: 'babel-loader',
            exclude: /node_modules/,// 或者include
            options: {
              cacheDirectory: true, // 缓存
              sourceMap: false
            }
        },
        {
            test: /\.vue$/,
            use: ['vue-loader'] // 需配合vueLoaderPlugin使用
        },
        {
        test: /\.css$/,
        use: [
        // 使用<style>将css-loader内部样式注入到我们的HTML页面.
            'style-loader',
            'css-loader',
        // css添加兼容前缀
          {
            loader: 'postcss-loader',
            options: {
              plugins: [require('autoprefixer')] // 必须配合autoprefixer插件使用,否则不起作用
            }
          }
        ]
      },
      {
        test: /\.stylus$/,
        use: [
             'style-loader',
            'css-loader'
          {
            loader: 'postcss-loader',
            options: {
              plugins: [require('autoprefixer')]
            }
          },
          'stylus-loader'
        ]
      },
      {
        test: /\.(jpe?g|png|gif)$/i, //图片文件
        use: [
          {
            loader: 'url-loader',
            options: {
              limit: 140,
              fallback: {
                loader: 'file-loader',
                options: {
                  name: path.posix.join('static', 'image/[name].[hash].[ext]')
                }
              }
            }
          }
        ]
      },
      {
        test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/, //媒体文件
        use: [
          {
            loader: 'url-loader',
            options: {
              limit: 102,
              fallback: {
                loader: 'file-loader',
                options: {
                  name: path.posix.join('static', 'media/[name].[hash].[ext]')
                }
              }
            }
          }
        ]
      },
      {
        test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/i, // 字体
        use: [
          {
            loader: 'url-loader',
            options: {
              limit: 1024,
              fallback: {
                loader: 'file-loader',
                options: {
                  name: path.posix.join('static', 'fonts/[name].[hash].[ext]')
                }
              }
            }
          }
       ] 
    }
}

Plugins 插件

下面介绍常规打包需要用到的插件

  • autoprefixer(配合postcss-loader使用)
  • clean-webpack-plugin(删除dist文件夹)
  • copy-webpack-plugin(复制static文件夹,不经过打包)
  • cross-env(设置环境变量,实现跨平台)
  • html-webpack-plugin(配置html模板)
  • mini-css-extract-plugin(压缩css)
  • webpack-merge
  • webpack-dev-server
  • vue-template-compiler
  • compression-webpack-plugin(代码压缩)
    {
        plugins: [
            <!--
                为html文件中引入的外部资源如script、link动态添加每次compile后的hash,防止引用缓存的外部文件问题, 可以生成创建html入口文件,比如单页面可以生成一个html文件入口,配置N个html-webpack-plugin可以生成N个页面入口
            -->
             new HtmlWebpackPlugin({
              filename: 'index1.html', 
              template: path.join(__dirname, 'index1.html'), //模板文件路径
              inject: true // 如果设置为 true 或者 body,所有的 javascript 资源将被放置到 body 元素的底部,'head' 将放置到 head 元素中。
            }),
            new HtmlWebpackPlugin({
              filename: 'index2.html', 
              template: path.join(__dirname, 'index2.html'),
              inject: true
            }),
            // 不用添加其他参数, 具体配置自己百度
            new CleanWebpackPlugin(),
            <!--
            将CSS提取为独立的文件。这个插件应该只用在 production 配置中,并且在loaders链中不使用 style-loader, 特别是在开发中使用HMR,因为这个插件暂时不支持HMR。
            webpack4需要自己使用压缩器,可以使用 optimize-css-assets-webpack-plugin 插件
            -->
            new MiniCssExtractPlugin({
              filename: path.posix.join('static/css', '[name].[hash].css')
            }),
            // 不能和optimize-css-assets-webpack-plugin一起使用,否则css不被压缩
             new CompressionWebpackPlugin({
                filename: '[dir][name].gz[ext][query]',
                algorithm: 'gzip',
                test: /\.(js|css|html)$/,
                threshold: 10240, // 大小阀值
                minRatio: 0.8
              })
        ]
    }

核心功能optimization

公共代码提取 ,webpack4弃用CommonsChunkPlugin,内置 optimization

作用: 提取被重复引入的文件,单独生成一个或多个文件,这样避免在多入口重复打包文件

配置项如下:

splitChunks: 主要就是根据不同的策略来分割打包出来的bundle

`chunks`:async(默认),all(推荐),initial.
  // 入口
  import "./a.js"  //同步加载
  import ('./b.js') //异步加载

  // b.js
  import vue from "vue" //
acyns: 分割异步打包的代码,打包出b和vue两个chunk
all: 分割异步同步代码(需要定义新规则,将同步的代码打包)
{
    splitChunks: {
        chunks: 'all',
        cacheGroups: {
            a : {
                name: 'vendor',
                test: /node_modules/
            }
        }
  }
}
initial: 同时打包异步同步,但是异步内部的引入将不再考虑,直接打包一起,会将vue和b的内容打在一起.

cacheGroups: 自定义配置决定生成的文件,缓存策略

  • test : 限制范围,正则匹配文件夹或文件
  • name : 打包的chunks的名字
  • priority : 优先级,多个分组冲突时决定把代码放在哪块
  • enforce: 强制生成
  • minSize 生成新的chunk的最小体积,默认30000B
  • minChunks 被entry引入的次数,默认1(为1时,适合分离node_modules里的第三方库)
  • automaticNameDelimiter 定义文件名称连接符,默认~

runtimeChunk: 作用是将包含chunks映射关系的list单独从app.js里提取出来,因为每一个chunk的id基本都是基于内容hash出来的,所以你每次改动都会影响它,如果不把它提取出来的话,等于app.js每次都会改变,缓存就失效了。

runtimeChunk: true, //runtimeChunk可以配置成true,single或者对象,用自动计算当前构建的一些基础chunk信息,类似之前版本中的manifest信息获取方式. minimizer: 可以自定义UglifyJsPlugin和一些配置,默认的压缩为uglifyjs-webpack-plugin

const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin')
const UglifyJsPlugin = require('uglifyjs-webpack-plugin')

{
    minimizer: [
      new UglifyJsPlugin({
        uglifyOptions: {
          compress: {
            drop_debugger: true,
            drop_console: true //生产环境自动删除console
          },
          warnings: false
        },
        sourceMap: false,
        parallel: true //使用多进程并行运行来提高构建速度。默认并发运行数:os.cpus().length - 1
      }),
      new OptimizeCssAssetsPlugin()
    ],
}

webpack3和webpack4打包后的文件分析

webpack3:

  • app.js:基本就是你实际编写的那个app.vue(.vue或.js?),没这个页面跑不起来.

  • vendor.js:vue-cli全家桶默认配置里面这个chunk就是将所有从node_modules/里require(import)的依赖都打包到这里,所以这个就是所有node_modules/下的被require(import)的js文件

  • manifest.js: 最后一个chunk,被注入了webpackJsonp的定义及异步加载相关的定义(webpack调用CommonsChunkPlugin处理后模块管理的核心,因为是核心,所以要第一个进行加载,不然会报错).

webpack4:

  • app.js 同上
  • vendor.js: 同上
  • runtime.js: 用自动计算当前构建的一些基础chunk信息,类似之前版本中的manifest信息获取方式

具体差异, 后面再说