加载中...

使用webpack4构建一个基于vuejs的开发编译环境,通过SplitChunks,webpackChunkName对代码对分割以及mini-css-extract-plugin分离css代码的实践

吴佳
2019-08-10 12:43:21
分类:WebPack
66
0
0

所需环境

开始之前,请各位给自己电脑安装一下Nodejs,具体安装方法这里我就不做讲解了,各位可以移步Node官网查看文档然后对应系统版本进行安装,以下是我的Node or Npm版本

{
  "engines": {
    "node": "10.16.0",
    "npm": "6.9.0"
  }
}

目录结构

.
├── dist
├── node_modules
├── public
│   ├── index.html
│   ├── images
├── build
│   ├── webpack.base.config.js
│   ├── webpack.dev.config.js
│   └── webpack.prod.config.js
├── package.json
├── package-lock.json
├── .babelrc
├── postcss.config.js
└── src
    ├── assets
    │   ├── js
    │   ├── style
    │   ├── images
    ├── pages
    │   ├── app.vue
    │   ├── home
    │   │   ├── index.vue
    │   ├── router
    │   │   ├── index.js
    └── main.js

这里我说一下几个重要目录,dist目录是webpack打包编译后输出目录,public目录是全局资源,build目录是webpack配置,src目录是我们开发的业务代码存放总目录,请各位按以上结构创建好各目录。
首先,如果你的目录还没有package.json文件,请通过以下方式创建一个文件,请打开你电脑的命令行工具,进入到对应项目目录执行以下命令

npm init -y

执行完成后,你的项目目录内就会生成出一个package.json的配置文件

package.json中的相关依赖

{
  "name": "webpack-demo",
  "version": "1.0.0",
  "description": "",
  "scripts": {
    "dev": "NODE_ENV=development webpack-dev-server --config ./build/webpack.dev.config.js --watch",
    "build": "NODE_ENV=production webpack --config ./build/webpack.prod.config.js",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "engines": {
    "node": "10.16.0",
    "npm": "6.9.0"
  },
  "devDependencies": {
    "@babel/core": "^7.5.5",
    "@babel/plugin-proposal-object-rest-spread": "^7.5.5",
    "@babel/plugin-syntax-dynamic-import": "^7.2.0",
    "@babel/plugin-transform-runtime": "^7.5.5",
    "@babel/preset-env": "^7.5.5",
    "@babel/runtime": "^7.5.5",
    "autoprefixer": "^9.6.1",
    "babel-loader": "^8.0.6",
    "clean-webpack-plugin": "^3.0.0",
    "core-js": "^3.1.4",
    "css-loader": "^3.1.0",
    "file-loader": "^4.1.0",
    "html-webpack-plugin": "^3.2.0",
    "image-webpack-loader": "^5.0.0",
    "mini-css-extract-plugin": "^0.8.0",
    "node-sass": "^4.12.0",
    "postcss-loader": "^3.0.0",
    "purify-css": "^1.2.5",
    "purifycss-webpack": "^0.7.0",
    "sass-loader": "^7.1.0",
    "style-loader": "^0.23.1",
    "url-loader": "^2.1.0",
    "vue-loader": "^15.7.1",
    "vue-style-loader": "^4.1.2",
    "vue-template-compiler": "^2.6.10",
    "webpack": "^4.38.0",
    "webpack-cli": "^3.3.6",
    "webpack-dev-server": "^3.7.2",
    "webpack-manifest-plugin": "^2.0.4",
    "webpack-merge": "^4.2.1"
  },
  "dependencies": {
    "vue": "^2.6.10",
    "vue-router": "^3.0.7",
    "vuex": "^3.1.1"
  },
  "postcss": {
    "plugins": {
      "autoprefixer": {}
    }
  },
  "browserslist": [
    "> 1%",
    "last 2 versions",
    "not ie >= 9",
    "ios >= 6",
    "android >= 4.0"
  ]
}

我们可以看到以上配置有很多东西,这里我们主要只关注三个地方,scripts,dependencies,devDependencies。
首先在scripts中设置了dev和build,开发和生产两种模式,在dev的命令中我们指定了一个文件./build/webpack.dev.config.js,这个文件是开发配置文件;然后build中指定了./build/webpack.prod.config.js,这个是生产配置文件,这两个文件里面都是我们这个项目的开发和打包的配置内容,也是今天的主要内容,后面我会详细给大家讲解这两个文件里面的配置。
dependencies是我们生产依赖,devDependencies是开发依赖。
然后可以把这些依赖复制到你的package.json配置文件中,然后执行以下命令拉取这些所需依赖

npm install

webpack配置

根据上方目录结构可以很清晰的看到项目的webpack配置相关的内容是存放在build目录下的,所以下面我来给大家讲以下这三个文件的作用:

  1. webpack.base.config.js 开发配置 or 生产配置的共用配置
  2. webpack.dev.config.js 开发配置
  3. webpack.prod.config.js 生产配置

webpack.base.config.js 配置

接下来,我们先看看共用配置,以下是具体配置信息

const path = require('path'),
webpack = require('webpack'),
VueLoaderPlugin = require('vue-loader/lib/plugin')

module.exports = {
  entry: './src/main.js',
  resolve: {
    alias: {
      vue: 'vue/dist/vue.js',
      "@": path.resolve(__dirname, '../src')
    },
    extensions: ['.ts', '.js', '.scss', '.css', '.png', '.jpg', '.jpeg', '.gif', '.vue', '.json']
  },
  module: {
    rules: [
      {
        test: /\.vue$/,
        loader: 'vue-loader'
      },
      {
        test: /\.js$/,
        loader: 'babel-loader',
        exclude: path.resolve(__dirname, '../node_modules'),
        include: path.resolve(__dirname, '../')
      },
      {
        test: /\.(png|jpe?g|gif|bmp|svg)$/i,
        use: [
          {
            loader: 'url-loader',
            options: {
              limit: 8192, // 小于8k将图片转换成base64
              name: '[path][name].[ext]?[hash:8]'
            }
          },
          {
            loader: 'image-webpack-loader', // 图片压缩
            options: {
              bypassOnDebug: true
            }
          }
        ]
      },
      {
        test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)$/i,
        use: [
          {
            loader: 'file-loader',
            options: {
              name: '[path][name].[ext]?[hash:8]' //path/to/file.png?e43b20c0
            }
          }
        ]
      },
      {
        test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/i,
        loader: 'url-loader',
        options: {
          limit: 8192,
          name: 'fonts/[name].[hash:8].[ext]'
        }
      }
    ]
  },
  plugins: [
    new VueLoaderPlugin(),

    //编译进度
    new webpack.ProgressPlugin(),
  ]
}

根据以上配置信息,我们可以看到,首先配置了项目编译的入口,指定了入口src目录下面的main.js文件,主要通过这个入口去编译我们业务代码的各个代码块。

resolve: {
    alias: {
      vue: 'vue/dist/vue.js',
      "@": path.resolve(__dirname, '../src')
    },
    extensions: ['.ts', '.js', '.scss', '.css', '.png', '.jpg', '.jpeg', '.gif', '.vue', '.json']
  },

resolve配置主要配置alias or extensions,它们分别作用:
alias 配置了目录的别名,方便后面引用模块时可直接通过别名去查找文件。
extensions 配置是为了在后面业务开发中通过import或者require去引入模块时,不需要去填入文件的后缀。
module 主要配置代码的编译与文件的各种loader处理,根据配置我们可以看到,主要分别处理了.vue文件的编译,.js文件的编译,对图片,字体,音乐文件的处理。
plugins 配置插件,首先我们加入了针对处理vue的loader插件,然后再加了一个显示编译进度的插件。

webpack.dev.config.js配置

接下来,我在下方列出开发环境相关配置信息

const merge = require('webpack-merge'), 
webpack = require('webpack'),
webpackBaseConfig = require('./webpack.base.config'),
HtmlWebpackPlugin = require('html-webpack-plugin'),
path = require('path')

module.exports = merge(webpackBaseConfig, {
  mode: 'development',
  devtool: 'inline-source-map',
  output: {
    filename: `[name].[hash:8].js`,
    chunkFilename: `[name].[hash:8].js`,
    path: path.resolve(__dirname, '../dist')
  },
  module: {
    rules: [
      {
        test: /\.(sa|sc|c)ss$/,
        use: [
          'vue-style-loader',
          {
            loader: 'css-loader',
            options: {
              sourceMap: true
            }
          },
          {
            loader: 'postcss-loader',
            options: {
              sourceMap: true,
            }
          },
          {
            loader: 'sass-loader',
            options: {
              sourceMap: true,
              // you can also read from a file, e.g. `variables.scss`
              data: `$color: red;`
            }
          }
        ]
      }
    ]
  },
  plugins: [
    //热更新
    new webpack.HotModuleReplacementPlugin(),

    new HtmlWebpackPlugin({
      title: 'webpack-demo',
      filename: path.resolve(__dirname, '../dist/index.html'),
      template: path.resolve(__dirname, '../public/index.html'),
      favicon: ''
    }),

    //持久化缓存
    new webpack.NamedModulesPlugin()
  ],
  devServer: {
    contentBase: path.resolve(__dirname, '../dist'),
    open: true,
    host: 'localhost',
    port: 8080,
    hot: true,
    compress: true,//服务器压缩
    proxy: {},
    progress: true
  }
})

以上配置可以看到,通过webpack-merge把webpack.base.config.js的配置合并进来了,然后补充了一些开发环境相关配置。
mode配置设置了当前打包的方式,为开发模式。
devtool配置主要作用是为了业务开发中,如果发生了逻辑错误,此配置会告诉开发者报错代码的具体位置,当然它的取值也有多个,所以具体请移步webpack的官方文档进行查看。
output输出编译后文件相关配置,里面的chunkFilename的作用稍后讲解生产配置时再做说明。
module配置中主要针对开发环境对css与scss编译处理,主要使用了vue-style-loader,css-loader,postcss-loader,sass-loader。
plugins主要配置热更新,html的处理以及缓存处理。
devServer是webpack-dev-server的相关配置,主要是启动一个开发服务,方便开发时能够实时看到编写内容

webpack.prod.config.js配置

const merge = require('webpack-merge'),
webpackBaseConfig = require('./webpack.base.config'),
HtmlWebpackPlugin = require('html-webpack-plugin'),
path = require('path'),
glob = require('glob'),
webpack = require('webpack'),
{ CleanWebpackPlugin } = require('clean-webpack-plugin'),
ManifestPlugin = require('webpack-manifest-plugin'),
MiniCssExtractPlugin = require('mini-css-extract-plugin'),
PurifyCSSPlugin = require('purifycss-webpack')
// UglifyJsPlugin = require('uglifyjs-webpack-plugin'),
// OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin'),

module.exports = merge(webpackBaseConfig, {
  mode: 'production',
  devtool: 'none',
  output: {
    filename: `[name].[chunkhash:8].js`,
    chunkFilename: `[name].[chunkhash:8].js`,
    path: path.resolve(__dirname, '../dist'),
    publicPath: "/"
  },
  optimization: {
     // minimizer: [
    //   new UglifyJsPlugin({
    //     cache: true,
    //     parallel: true,
    //     sourcMap: true
    //   }),
    //   new OptimizeCSSAssetsPlugin({}),
    // ],
    splitChunks: {
      chunks: "all",
      cacheGroups: {
        vendors: {
        test: /[\\/]node_modules[\\/]/, // 匹配node_modules目录下的文件
        priority: -10 // 优先级配置项
      },
      default: {
        minChunks: 2,
        priority: -20, // 优先级配置项
        reuseExistingChunk: true
      }
      }
    }
  },
  module: {
    rules: [
      {
        test: /\.(sa|sc|c)ss$/,
        use: [
          MiniCssExtractPlugin.loader,
          {
            loader: 'css-loader',
            options: {}
          },
          {
            loader: 'postcss-loader',
            options: {}
          },
          {
            loader: 'sass-loader',
            options: {
              indentedSyntax: true,
              // you can also read from a file, e.g. `variables.scss`
              data: `$color: red;`
            }
          },
        ]
      },
    ]
  },
  plugins: [
    //持久化缓存
    new webpack.HashedModuleIdsPlugin(),

    //清目录
    new CleanWebpackPlugin(),
    new HtmlWebpackPlugin({
      title: 'webpack-demo',
      filename: path.resolve(__dirname, '../dist/index.html'),
      template: path.resolve(__dirname, '../public/index.html'),
      minify: {
        removeRedundantAttributes: true, // 删除多余的属性
        collapseWhitespace: true, // 折叠空白区域
        removeAttributeQuotes: true, // 移除属性的引号
        removeComments: true, // 移除注释
        collapseBooleanAttributes: true // 省略只有 boolean 值的属性值 例如:readonly checked
      },
      favicon: ''
    }),

    //提取css
    new MiniCssExtractPlugin({
      filename: 'assets/style/[name].[chunkhash:8].css',
      chunkFileName: 'assets/style/[name].[chunkhash:8].css',
      allChunks: true
    }),

    //删除多余的CSS
    new PurifyCSSPlugin({
      paths: glob.sync(path.join(__dirname, '../public/*.html'))
    }),

    //生成manifest清单
    new ManifestPlugin()
  ]
})

生产配置我就不一一讲解了,这里我就只说重要部分,当然重要的部分就是对于一些压缩和优化上的操作,并且生产环境是不需要服务的,它与开发环境最大的区别就是生产环境会分割代码,分离css,压缩代码,做一些优化上的处理,而开发环境是不会特意做这些操作的。
首先,它和开发配置一样,把webpack.base.config.js合并进来了,然后扩展了新的配置,设置了mode为生产环境,devtool给关闭了,你也可以开启devtool但是这会影响打包速度,下面主要说一下几个配置:
optimization配置是webpack4才加的,它的作用是用来分割公共代码的,当webpack的mode设为生产模式时,optimization的配置会默认开启。

optimization的参数说明
  • chunks:表示从哪些chunks里面抽取代码,除了三个可选字符串值 initial、async、all 之外,还可以通过函数来过滤所需的 chunks;
  • minSize:表示抽取出来的文件在压缩前的最小大小,默认为 30000;
  • maxSize:表示抽取出来的文件在压缩前的最大大小,默认为 0,表示不限制最大大小;
  • minChunks:表示被引用次数,默认为1;上述配置commons中minChunks为2,表示将被多次引用的代码抽离成commons。

值得注意的是,如果没有修改minSize属性的话,而且公用的代码size小于30KB的话,它就不会分割成一个单独的文件。在真实情形下,这是合理的,因为(如分割)并不能带来性能的提升,反而使得浏览器多了一次对资源的请求。

  • maxAsyncRequests:最大的按需(异步)加载次数,默认为 5;
  • maxInitialRequests:最大的初始化加载次数,默认为 3;
  • automaticNameDelimiter:抽取出来的文件的自动生成名字的分割符,默认为 ~;
  • name:抽取出来文件的名字,默认为 true,表示自动生成文件名;
  • cacheGroups: 缓存组。(这才是配置的关键)

缓存组会继承splitChunks的配置,但是 test、priorty和reuseExistingChunk只能用于配置缓存组 。cacheGroups是一个对象,按上述介绍的键值对方式来配置即可,值代表对应的选项。除此之外,所有上面列出的选择都是可以用在缓存组里的:chunks, minSize, minChunks, maxAsyncRequests, maxInitialRequests, name。可以通过optimization.splitChunks.cacheGroups.default: false禁用default缓存组。 默认缓存组的优先级(priotity)是负数,因此所有自定义缓存组都可以有比它更高优先级(译注:更高优先级的缓存组可以优先打包所选择的模块)(默认自定义缓存组优先级为0)

chunkFilename

个人理解chunkFilename就是未被列在entry中,但有些场景需要被打包出来的文件命名配置。比如按需加载(异步)模块的时候,这样的文件是没有被列在entry中的使用CommonJS的方式异步加载模块。比如:

require.ensure(["modules/index.js"], function(require) {
    var a = require("modules/index.js");
    // ...
}, 'app');

异步加载的模块是要以文件形式加载,所以这时生成的文件名是以chunkname配置的,生成出的文件名就是app.min.js。
require.ensure 第三个参数是给这个模块命名,否则 chunkFilename: "[name].min.js"中的 [name]是一个自动分配的、可读性很差的id。
当然,异步加载模块的写法还有一种方式,就是通过es6的import。比如:

import(
        /* webpackChunkName: 'pages/home/index' */
      '@/pages/home')

以上代码执行后就会输出一个pages目录然后home目录里面有一个index.acw9gpl0m.js文件,当然chunkhash我是随便输入的,这个以打包的chunkhash为准。
如果使用这种方式需要添加一个.babelrc文件,具体配置

{
  "presets": [
    "@babel/preset-env"
  ],
  "plugins": [
    "@babel/plugin-proposal-object-rest-spread",
    "@babel/plugin-transform-runtime",
    "@babel/plugin-syntax-dynamic-import"
  ]
}
MiniCssExtractPlugin

miniCssExtractPlugin将CSS提取为独立的文件的插件,对每个包含css的js文件都会创建一个CSS文件,支持按需加载css和sourceMap
只能用在webpack4中,对比另一个插件 extract-text-webpack-plugin优点:

  • 异步加载
  • 不重复编译,性能更好
  • 更容易使用
  • 只针对CSS

这里目前配置是没有配置压缩的,如果需要生产压缩,可以使用optimize-css-assets-webpack-plugin 插件。 设置 optimization.minimizer 覆盖webpack默认提供的,确保也指定一个JS压缩器,具体配置可见optimization配置的注释部分代码,需自行拉取所需依赖并引入。
关于MiniCssExtractPlugin插件的具体参数,我这里就不做介绍了,可去npm上自行了解。

补充

关于postcss-loader,是为了给一些css3代码加浏览器兼容前缀,所以在目录中创建了一个postcss.config.js配置文件,具体配置内容如下:

module.exports = {
  plugins: [
    require('autoprefixer')
  ]
}

Vue相关代码

main.js文件

import "core-js/modules/es.promise";
import "core-js/modules/es.array.iterator";
import Vue from 'vue'
import router from '@/router/index'
import App from '@/pages/app'

Vue.config.productionTip = false

new Vue({
  router,
  render: h => h(App),
}).$mount('#app')

//热更新,如果更改业务代码,无刷新自动局部更新视图
if (module.hot) {
  module.hot.accept()
}

router/index.js

import Vue from 'vue'
import VueRouter from 'vue-router';
Vue.use(VueRouter)

const router = new VueRouter({
  routes: [
    {
      path: '/',
      component: () => import(
        /* webpackChunkName: 'pages/home/index' */
        '@/pages/home')
    }
  ]
})

export default router

pages/app.vue

<style lang="scss">
.index {
  color: red;
}
</style>


<template>
<section class="index">
  <router-view></router-view>
</section>
</template>

<script>
export default {
  name: 'index',
  data () {
    return {

    }
  }
}
</script>

pages/home/index.vue

<style lang="scss">
.home {
  color: blue;
}
</style>

<template>
<section class="home">{{content}}</section>
</template>

<script>
export default {
  name: 'home',
  data () {
    return {
      content: 'home页面内容'
    }
  }
}
</script>

最后

至此,关于webpack4构建Vue环境,通过SplitChunks,webpackChunkName对代码对分割以及mini-css-extract-plugin分离css代码的所有内容以讲解完。如果有不对的地方,请在下方评论区域留言,我们共同讨论。
如需现在尝试运行,请点击webpack-demo

0

发表评论(共0条评论)

请输入评论内容
啊哦,暂无评论数据~