Skip to content

AmazingZ-sys/webpackLearn

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

7 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

webpack学习实战

前言

随着前端的发展,只会傻傻使用脚手架,然后npm run servenpm run build显然是不够的,在面试中也常常会有面试官问道会用webpack吗?会自定义进行配置管理吗?学会webpack往往能使你在面试中脱颖而出,废话不多说,我们开始走进webpack的世界。

本文仅供参考,如果错误烦请指正,蟹蟹

项目地址:GitHub

入门

webpack可以看成一个模块打包工具,分析目录结构,处理模块化依赖,转换成浏览器“认识”的代码。

  • 代码转换:TypeScript编译转换成JavaScriptSCSS,LESS,SASS编译转换成CSS
  • 文件优化:压缩JavaScriptCSSHTML代码,压缩合并图片
  • 代码分割:提取多个页面的公共代码,提取首屏不需要执行的部分代码让其异步加载
  • 模块合并:在采用模块化的项目里会有多个模块和文件,需要构建功能把模块分类合并成一个文件
  • 自动刷新:使用热加载插件,监听本地代码的变化,自动重新构建代码、刷新浏览器

1.初始化项目

新建一个空文件夹,初始化npm

// npm
npm init
// yarn
yarn init

webpack是运行在node环境中的,我们需要安装一下两个包

// npm
npm i -D webpack webpack-cli
// yarn
yarn add -D webpack wbpack-cli

新绛一个文件夹src,然后新建一个文件main.js,写一点代码进行测试

console.log("我在学习webpack");

配置package.json命令

// 运行 yarn run build 命令时将会找到 src 目录下的 main.js 当做入口文件进行打包操作
"scripts": {
    "build": "webpack src/main.js"
 }

执行

// npm
npm run build
// yarn 
yarn run build

此时如果生成了一个dist文件夹,并且内部含有main.js文件说明已经打包成功,这时候可能会出现如下警告(因为我们没有配置开发环境与生产环境,但是并不影响打包的执行):

2.开始我们的自定义配置

上面的一个简单示例只是webpack默认的配置,在开发中往往我们需要自定义一些配置,新建一个build文件夹,里面新建一个webpack.config.js文件

// webpack.config.js

const path = require('path');
module.exports = {
    mode:'development', // 开发模式
    entry: path.resolve(__dirname,'../src/main.js'),    // 入口文件
    output: {
        filename: 'zhizhi.js',      // 打包后的文件名称
        path: path.resolve(__dirname,'../dist')  // 打包后的目录
    }
}
  • entry:入口文件,webpack会找到该文件进行解析
  • output:输出文件配置
  • path:输出文件的位置(要将文件输出到哪个目录)
  • filename:输出文件的名字

更改我们的命令为

"scripts": {
    "build": "webpack --config build/webpack.config.js"
},

执行

// npm 
npm run build
// yarn
yarn run build

此时生成了如下文件

3.配置html

js文件打包完成,但是我们不能每次都在相应的html模板中手动引入打包之后的js文件

有的老板对这里的可能会有疑问,我们打包好的js文件名不都是一样的吗?不是引入一次就行了吗?然而我们在开发中为了避免浏览器缓存导致页面更新不及时,往往会这样配置

// 这里的 [name] 为你的文件名称,如入口文件名为main.js [name] 就为main
// 而[hash:8] 表示在文件名之后加上8位的 hash 值,此时你每次打包之后的文件名都是不一样的
module.exports = {
    // 省略其他配置
    output: {
      filename: '[name].[hash:8].js',      // 打包后的文件名称
      path: path.resolve(__dirname,'../dist')  // 打包后的目录
    }
}

此时生成的文件为:

因为每次生成的js文件名都不一样,而我们都需要将js文件引入到html中,这时候我们就需要一个插件来帮助我们html-webpack-plugin

安装html-webpack-plugin

// npm 
npm i -D html-webpack-plugin
// yarn 
yarn add -D html-webpack-plugin

新建一个与build同级的文件夹public,在里面新建一个index.html,然后修改配置文件webpack.config.js

// webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
    mode:'development', // 开发模式
    entry: path.resolve(__dirname,'../src/main.js'),    // 入口文件
    output: {
      filename: '[name].[hash:8].js',      // 打包后的文件名称
      path: path.resolve(__dirname,'../dist')  // 打包后的目录
    },
    plugins:[
      new HtmlWebpackPlugin({
        template:path.resolve(__dirname,'../public/index.html')
      })
    ]
}

此时生成的目录和效果如下

3.1 多入口文件的开发

生成多个html-webpack-plugin实例来解决

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
    mode:'development', // 开发模式
    entry: {
      main:path.resolve(__dirname,'../src/main.js'),
      header:path.resolve(__dirname,'../src/header.js')
  }, 
    output: {
      filename: '[name].[hash:8].js',      // 打包后的文件名称
      path: path.resolve(__dirname,'../dist')  // 打包后的目录
    },
    plugins:[
      new HtmlWebpackPlugin({
        template:path.resolve(__dirname,'../public/index.html'),
        filename:'index.html',
        title:"main",
        hash:true,
        minify: {
            removeAttributeQuotes: true
        }
        chunks:['main'] // 与入口文件对应的模块名
      }),
      new HtmlWebpackPlugin({
        template:path.resolve(__dirname,'../public/header.html'),
        filename:'header.html',
        title:"header",
        hash:true,
        minify: {
            removeAttributeQuotes: true
        }
        chunks:['header'] // 与入口文件对应的模块名
      }),
    ]
}

多个入口配置:

  • templatehtml模板的路径地址
  • filename:生成的文件名
  • title:传入的参数
  • chunks:需要引入的chunk
  • hash:在引入的JS文件中加入hash值(引入时候文件名之后加上?xxxxxxxxx
  • removeAttributeQuotes:去掉引号,减少文件大小

执行npm run build 或者 yarn run build此时生成的目录和文件如下

3.2 Clean-webpack-plugin

在我们每次执行打包之后会发现dist文件夹里面会残留上次打包的文件,我们会使用clean-webpack-plugin插件来帮助我们在打包输出前清空目标文件夹

先安装clean-webpack-plugin

// npm 
npm i -D clean-webpack-plugin
// yarn 
yarn add -D clean-webpack-plugin

修改配置文件webpack.config.js

const {CleanWebpackPlugin} = require('clean-webpack-plugin')
module.exports = {
    // ...省略其他配置
    plugins:[new CleanWebpackPlugin()]
}

4.引用CSS

我们的入口文件是js,所以我们在入口js文件中引入我们的CSS文件

src目录新建assets文件夹,里面新建index.css 和 index.less文件,然后在main.js文件中引入css文件

因为我们引入了CSS文件,所以需要一些loader来解析我们的CSS文件

// npm 
npm i -D style-loader css-loader less less-loader   // 如果我们使用了less来构建样式,则需要多安装两个,scss/sass同理
// yarn 
yarn add -D style-loader css-loader less less-loader
  • css-loader:支持css中的import
  • style-loader:把cssstyle标签的形式引入到html
  • sass-loader,less-loader,scss-loader:转译为css
  • node-sasssass转译依赖,其他预处理语言同理

修改配置文件webpack.config.js

// webpack.config.js
module.exports = {
    // ...省略其他配置
    module:{
      rules:[
        {
          test:/\.css$/,
          use:['style-loader','css-loader'], // 从右向左解析原则
          exclude: /node_modules/
        },
        {
          test:/\.less$/,
          use:['style-loader','css-loader','less-loader'], // 从右向左解析原则
          exclude: /node_modules/
        }
      ]
    }
} 
  • test:一个正则表达式,匹配文件名
  • use:一个数组,里面放入需要执行的loader,从右往左解析
  • exclude:取消匹配node_modules里面的文件

此时浏览器打开index.html可以看到CSSstyle标签的形式嵌入到页面中

4.1 为CSS添加浏览器前缀兼容各内核浏览器

安装postcss-loader 和 autoprefixer

// npm 
npm i -D postcss-loader autoprefixer
// yarn 
yarn add -D postcss-loader autoprefixer

修改配置webpack.config.js

// webpack.config.js
module.exports = {
    module:{
        rules:[
            {
                test:/\.less$/,
                use:['style-loader','css-loader','postcss-loader','less-loader'] // 从右向左解析原则
           }
        ]
    }
} 

修改package.json文件(不进行配置可能会添加前缀失败)

{
	// 忽略其他配置
  "browserslist": [
    "last 1 version",
    "> 1%",
    "IE 10"
  ],
}

接下来我们还需要引入autoprefixer使其生效,这里有两种方式

1.在项目跟目录创建一个postcss.config.js,配置如下

module.exports = {
    plugins: [require('autoprefixer')]  // 引用该插件即可了
}

2.直接在webpack.config.js文件中配置

// webpack.config.js
module.exports = {
    //...省略其他配置
    module:{
        rules:[{
            test:/\.less$/,
            use:['style-loader','css-loader',{
                loader:'postcss-loader',
                options:{
                    plugins:[require('autoprefixer')]
                }
            },'less-loader'] // 从右向左解析原则
        }]
    }
}

这时候我们已经完成了csshtml文件中的引入,但是如果样式文件很多,全部添加到html中会显得混乱。而我们可以通过插件mini-css-extract-plugin来拆分css

4.2 拆分css

安装mini-css-extract-plugin

// npm
npm i -D mini-css-extract-plugin
// yarn 
yarn add -D mini-css-extract-plugin

在webpack 4.0以前,我们通过extract-text-webpack-plugin插件,把css样式从js文件中提取到单独的css文件中,webpack 4.0之后,官方推荐使用mini-css-extract-plugin插件来打包css文件

配置文件如下

const MiniCssExtractPlugin = require("mini-css-extract-plugin");
module.exports = {
  //...省略其他配置
  module: {
    rules: [
      {
        test: /\.less$/,
        use: [
           MiniCssExtractPlugin.loader,
          'css-loader',
          'less-loader'
        ],
      }
    ]
  },
  plugins: [
    new MiniCssExtractPlugin({
        filename: "[name].[hash].css",
        chunkFilename: "[id].css",
    })
  ]
}

运行npm run build 或者 yarn run build打包之后文件目录如下

在这里遇到一个坑,在修改配置文件的时候没有删除style-loader,然后运行打包报错,后来想起来style-loader是将css文件通过style标签内嵌到页面中,而mini-css-extract-plugin是将css文件拆分成独立文件,两个插件冲突所以报错,将style-loader删除就好了

4.3 拆分多个css

上面我们用到的mini-css-extract-plugin是将所有的css样式合并为一个css文件,如果我们要拆分多个css文件,需要用到extract-text-webpack-plugin,而目前mini-css-extract-plugin还不支持此功能,我们需要安装@next版本的extract-text-webpack-plugin

安装:

// npm 
npm i -D extract-text-webpack-plugin@next
// yarn
yarn add -D extract-text-webpack-plugin@next

修改webpack.config.js配置

// webpack.config.js

const path = require('path');
const ExtractTextWebpackPlugin = require('extract-text-webpack-plugin')
let indexLess = new ExtractTextWebpackPlugin('index.less');
let indexCss = new ExtractTextWebpackPlugin('index.css');
module.exports = {
    module:{
      rules:[
        {
          test:/\.css$/,
          use: indexCss.extract({
            use: ['css-loader']
          })
        },
        {
          test:/\.less$/,
          use: indexLess.extract({
            use: ['css-loader','less-loader']
          })
        }
      ]
    },
    plugins:[
      indexLess,
      indexCss
    ]
}

打包之后的目录如图所示

5.打包图片、字体、媒体等文件

file-loader就是将文件在进行一些处理之后(主要是处理文件名和路径、解析文件url),并将文件移动到输出的目录中

url-loader一般会与file-loader搭配使用,功能与file-loader类似,如果文件小于限制的大小,则会返回文件的base64编码,否则使用file-loader将文件移动到输出目录中

// webpack.config.js
module.exports = {
  // 省略其它配置 ...
  module: {
    rules: [
      // ...
      {
        test: /\.(jpe?g|png|gif)$/i, //图片文件
        use: [
          {
            loader: 'url-loader',
            options: {
              limit: 10240,
              fallback: {
                loader: 'file-loader',
                options: {
                    name: 'img/[name].[hash:8].[ext]'
                }
              }
            }
          }
        ]
      },
      {
        test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/, //媒体文件
        use: [
          {
            loader: 'url-loader',
            options: {
              limit: 10240,
              fallback: {
                loader: 'file-loader',
                options: {
                  name: 'media/[name].[hash:8].[ext]'
                }
              }
            }
          }
        ]
      },
      {
        test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/i, // 字体
        use: [
          {
            loader: 'url-loader',
            options: {
              limit: 10240,
              fallback: {
                loader: 'file-loader',
                options: {
                  name: 'fonts/[name].[hash:8].[ext]'
                }
              }
            }
          }
        ]
      },
    ]
  }
}

6.用babel转义js文件

为了使我们的js代码兼容更多的浏览器环境,我们需要将ES6和更高版本的语法进行转义

安装

// npm 
npm i -D babel-loader @babel/preset-env @babel/core
// yarn 
yarn add -D babel-loader @babel/preset-env @babel/core
  • 注意babel-loaderbabel-core的版本对应关系
    • babel-loader8.x对应babel-core7.x
    • babel-loader7.x对应babel-core6.x

修改webpack.config.js配置

// webpack.config.js
module.exports = {
    // 省略其它配置 ...
    module:{
        rules:[
          {
            test:/\.js$/,
            use:{
              loader:'babel-loader',
              options:{
                presets:['@babel/preset-env']
              }
            },
            exclude:/node_modules/
          },
       ]
    }
}

举个栗子:

修改配置后我们在main.js中写入

运行打包之后找到打包后的main.xxxxxxxx.js文件,打开之后找到我们的console

不难发现,上面的babel-loader只会将ES6/7/8语法转为ES5语法,但是对新的api并不会转换,例如:Promise, Generator, Set, Maps, Proxy等

此时我们需要借助babel-polyfill来进行转换

安装

// npm 
npm i -D @babel/polyfill
// yarn 
yarn add -D @babel/polyfill

修改webpack.config.js配置

// webpack.config.js
const path = require('path')
module.exports = {
    entry: ["@babel/polyfill",path.resolve(__dirname,'../src/index.js')],    // 入口文件
}
// 多个入口文件时配置如下
entry: {
        main:["@babel/polyfill",path.resolve(__dirname,'../src/main.js')],
        header:["@babel/polyfill",path.resolve(__dirname,'../src/header.js')],
    },

Babel的配置建议在根目录下新建一个.babelrc文件

{
    "presets": [
        "env",
        "stage-0", 
        "react"
    ],
    "plugins": [
        "transform-runtime",
        "transform-decorators-legacy",
        "add-module-exports"
    ]
}
  • presets:预设,一个预设包含多个插件,起到方便作用,不用引用多个文件
  • env:只转换新的语法,例如const,let,()=>等,不转换如Promise,Proxy,Generator,Set,Maps,Symbol
  • stage-0:es7提案转码规则,有0 1 2 3 几个阶段,阶段0包含了所有的
  • react:转换reactjsx语法
  • plugins:插件,可以自己开发插件,转换代码(依赖于AST抽象语法树)
  • transform-runtime:转换新语法,自动引入polyfill插件,另外可以避免污染全局变量
  • transform-decorators-legacy:支持装饰器
  • add-module-exprots:转译export default{};添加上module.exports = exports.default支持commonjs

到现在为止我们对webpack有了个初步了解,但是想要熟练运用,我们需要一个系统的实战,开始摆脱脚手架尝试搭建自己的Vue项目吧

搭建Vue开发环境

上面的栗子🌰已经帮我们实现了打包css、图片、js、HTML等文件,但是我们还需要一下几种配置

1.解析.vue文件

安装

// npm 
npm i -D vue-loader vue-template-compiler vue-style-loader
npm i -S vue
// yarn 
yarn add -D vue-loader vue-template-compiler vue-style-loader
yarn add vue

vue-loader用于解析.vue文件

vue-template-compiler用于编辑template模板

修改webpack.config.js配置

const vueLoaderPlugin = require('vue-loader/lib/plugin')
module.exports = {
    module:{
        rules:[{
            test:/\.vue$/,
            use:['vue-loader']
        },]
     },
    resolve:{
        alias:{
          'vue$':'vue/dist/vue.runtime.esm.js',
          ' @':path.resolve(__dirname,'../src')
        },
        extensions:['*','.js','.json','.vue']
   },
   plugins:[
        new vueLoaderPlugin()
   ]
}

2.配置webpack-dev-server进行热更新

安装

//npm 
npm i -D webpack-dev-server
// yarn 
yarn add -D webpack-dev-server

修改webpack.config.js配置

const Webpack = require('webpack')
module.exports = {
  // ...省略其他配置
  devServer:{
    port:3000,
    hot:true,
    contentBase:'../dist'
  },
  plugins:[
    new Webpack.HotModuleReplacementPlugin()
  ]
}

完整配置如下

// vue环境

const path = require('path');
const {CleanWebpackPlugin} = require('clean-webpack-plugin')
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const HtmlWebpackPlugin = require('html-webpack-plugin')
const ExtractTextWebpackPlugin = require('extract-text-webpack-plugin')
let indexLess = new ExtractTextWebpackPlugin('index.less');
let indexCss = new ExtractTextWebpackPlugin('index.css');
const vueLoaderPlugin = require('vue-loader/lib/plugin')
const Webpack = require('webpack')

module.exports = {
    mode: 'development', // 开发模式
    entry: {
        main: ["@babel/polyfill", path.resolve(__dirname, '../src/main.js')],
        header: ["@babel/polyfill", path.resolve(__dirname, '../src/header.js')],
    },
    output: {
        filename: '[name].[hash:8].js',      // 打包后的文件名称
        path: path.resolve(__dirname, '../dist')  // 打包后的目录
    },
    plugins: [
        new vueLoaderPlugin(),
        new CleanWebpackPlugin(),
        new Webpack.HotModuleReplacementPlugin(),
        new HtmlWebpackPlugin({
            template: path.resolve(__dirname, '../public/index.html'),
            filename: 'index.html',
            chunks: ['main'] // 与入口文件对应的模块名
        }),
        new HtmlWebpackPlugin({
            template: path.resolve(__dirname, '../public/header.html'),
            filename: 'header.html',
            chunks: ['header'] // 与入口文件对应的模块名
        }),
    ],
    resolve: {
        alias: {
            'vue$': 'vue/dist/vue.runtime.esm.js',
            ' @': path.resolve(__dirname, '../src')
        },
        extensions: ['*', '.js', '.json', '.vue']
    },

    module: {
        rules: [
            {
                test: /\.vue$/,
                use: ['vue-loader']
            },
            {
                test: /\.js$/,
                use: {
                    loader: 'babel-loader',
                    options: {
                        presets: ['@babel/preset-env']
                    }
                },
                exclude: /node_modules/
            },
            {
                test: /\.css$/,
                use: ['vue-style-loader', 'css-loader', {
                    loader: 'postcss-loader',
                    options: {
                        plugins: [require('autoprefixer')]
                    }
                }]
            },
            {
                test: /\.less$/,
                use: ['vue-style-loader', 'css-loader', {
                    loader: 'postcss-loader',
                    options: {
                        plugins: [require('autoprefixer')]
                    }
                }, 'less-loader']
            }
        ]
    },
    devServer:{
        port:3000,
        hot:true,
        contentBase:'../dist'
    },
}

3.配置打包命令

打包文件配置完毕,接下来我们测试一下,在src文件夹里新建vueMain.js

src文件夹里新建一个App.vue文件

public文件夹下新建index.html

此时运行npm run dev 或者 yarn run dev 它会在本地跑一个node服务,端口为3000并自动打开浏览器访问:

4.区分开发环境和生产环境

在实际应用中,我们需要区分开发环境和生产环境,我们在原来webpack.config.js文件的基础上再新增两个文件

  • webpack.dev.js开发环境的配置文件

    开发环境需要的主要是热更新,不需要压缩代码,拥有完整的resourceMap

  • webpack.prod.js生产环境配置文件

    生产环境需要的是代码压缩、提取CSS文件、合理的resourceMap、分割代码

    需要安装一下依赖:

    • webpack-merge 合并配置
    • copy-webpack-plugin 拷贝静态资源
    • optimize-css-assets-webpack-plugin css压缩
    • uglifyjs-webpack-plugin js压缩

    webpack mode 设置 production 的时候回自动压缩js代码。原则上不需要引入uglifyjs-webpack-plugin进行重复工作,但是optimize-css-assets-webpack-plugin 压缩css 的同时会破坏原有的js压缩,所以这里我们需要引入uglifyjs进行压缩

4.1 webpack.config.js

const path = require('path')
const {CleanWebpackPlugin} = require('clean-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const vueLoaderPlugin = require('vue-loader/lib/plugin')
const MiniCssExtractPlugin = require("mini-css-extract-plugin")
const devMode = process.argv.indexOf('--mode=production') === -1;
module.exports = {
  entry:{
    main:path.resolve(__dirname,'../src/main.js')
  },
  output:{
    path:path.resolve(__dirname,'../dist'),
    filename:'js/[name].[hash:8].js',
    chunkFilename:'js/[name].[hash:8].js'
  },
  module:{
    rules:[
      {
        test:/\.js$/,
        use:{
          loader:'babel-loader',
          options:{
            presets:['@babel/preset-env']
          }
        },
        exclude:/node_modules/
      },
      {
        test:/\.vue$/,
        use:[{
          loader:'vue-loader',
          options:{
            compilerOptions:{
              preserveWhitespace:false
            }
          }
        }]
      },
      {
        test:/\.css$/,
        use:[{
          loader: devMode ? 'vue-style-loader' : MiniCssExtractPlugin.loader,
          options:{
            publicPath:"../dist/css/",
            hmr:devMode
          }
        },'css-loader',{
          loader:'postcss-loader',
          options:{
            plugins:[require('autoprefixer')]
          }
        }]
      },
      {
        test:/\.less$/,
        use:[{
          loader:devMode ? 'vue-style-loader' : MiniCssExtractPlugin.loader,
          options:{
            publicPath:"../dist/css/",
            hmr:devMode
          }
        },'css-loader','less-loader',{
          loader:'postcss-loader',
          options:{
            plugins:[require('autoprefixer')]
          }
        }]
      },
      {
        test:/\.(jep?g|png|gif)$/,
        use:{
          loader:'url-loader',
          options:{
            limit:10240,
            fallback:{
              loader:'file-loader',
              options:{
                name:'img/[name].[hash:8].[ext]'
              }
            }
          }
        }
      },
      {
        test:/\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
        use:{
          loader:'url-loader',
          options:{
            limit:10240,
            fallback:{
              loader:'file-loader',
              options:{
                name:'media/[name].[hash:8].[ext]'
              }
            }
          }
        }
      },
      {
        test:/\.(woff2?|eot|ttf|otf)(\?.*)?$/i,
        use:{
          loader:'url-loader',
          options:{
            limit:10240,
            fallback:{
              loader:'file-loader',
              options:{
                name:'media/[name].[hash:8].[ext]'
              }
            }
          }
        }
      }
    ]
  },
  resolve:{
    alias:{
      'vue$':'vue/dist/vue.runtime.esm.js',
      ' @':path.resolve(__dirname,'../src')
    },
    extensions:['*','.js','.json','.vue']
  },
  plugins:[
    new CleanWebpackPlugin(),
    new HtmlWebpackPlugin({
      template:path.resolve(__dirname,'../public/index.html')
    }),
    new vueLoaderPlugin(),
    new MiniCssExtractPlugin({
      filename: devMode ? '[name].css' : '[name].[hash].css',
      chunkFilename: devMode ? '[id].css' : '[id].[hash].css'
    })
  ]
}

4.2 webpack.dev.js

const Webpack = require('webpack')
const webpackConfig = require('./webpack.config.js')
const WebpackMerge = require('webpack-merge')
module.exports = WebpackMerge(webpackConfig,{
  mode:'development',
  devtool:'cheap-module-eval-source-map',
  devServer:{
    port:3000,
    hot:true,
    contentBase:'../dist'
    host: 'localhost',
    overlay: true,
    compress: true,
    open:true,
    inline: true,
    progress: true
  },
  devtool:"inline-source-map",
  plugins:[
    new Webpack.HotModuleReplacementPlugin(),
    new webpack.NamedModulesPlugin(),
  ]
})
  • contentBase: 静态文件地址
  • port: 端口号
  • host: 主机
  • overlay: 如果出错,则在浏览器中显示出错误
  • compress: 服务器返回浏览器的时候是否启动gzip压缩
  • open: 打包完成自动打开浏览器
  • hot: 模块热替换 需要webpack.HotModuleReplacementPlugin插件
  • inline: 实时构建
  • progress: 显示打包进度
  • devtool: 生成代码映射,查看编译前代码,利于找bug
  • webpack.NamedModulesPlugin: 显示模块的相对路径

4.3 webpack.prod.js

使用uglifyjs-webpack-plugin

const path = require('path')
const webpackConfig = require('./webpack.config.js')
const WebpackMerge = require('webpack-merge')
const CopyWebpackPlugin = require('copy-webpack-plugin')
const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin')
const UglifyJsPlugin = require('uglifyjs-webpack-plugin')
module.exports = WebpackMerge(webpackConfig,{
  mode:'production',
  devtool:'cheap-module-source-map',
  plugins:[
    new CopyWebpackPlugin([{
      from:path.resolve(__dirname,'../public'),
      to:path.resolve(__dirname,'../dist')
    }]),
  ],
  optimization:{
    minimizer:[
      new UglifyJsPlugin({//压缩js
        cache:true,
        parallel:true,
        sourceMap:true
    }),
    new OptimizeCssAssetsPlugin({})
    ],
    splitChunks:{
      chunks:'all',
      cacheGroups:{
        libs: {
          name: "chunk-libs",
          test: /[\\/]node_modules[\\/]/,
          priority: 10,
          chunks: "initial" // 只打包初始时依赖的第三方
        }
      }
    }
  }
})

使用webpack-parallel-uglify-plugin

const path = require('path')
const webpackConfig = require('./webpack.config.js')
const WebpackMerge = require('webpack-merge')
const WebpackParallelUglifyPlugin = require('webpack-parallel-uglify-plugin');
module.exports = WebpackMerge(webpackConfig,{
  mode:'production',
  devtool:'cheap-module-source-map',
  plugins:[
    new WebpackParallelUglifyPlugin(
        {
            uglifyJS: {
                mangle: false,
                output: {
                    beautify: false,
                    comments: false
                },
                compress: {
                    warnings: false,
                    drop_console: true,
                    collapse_vars: true,
                    reduce_vars: true
                }
            }
        }
    ),
  ],
})

uglifyJS配置:

  • mangle: 是否混淆代码
  • output.beautify: 代码压缩成一行 true为不压缩 false压缩
  • output.comments: 去掉注释
  • compress.warnings: 在删除没用到代码时 不输出警告
  • compress.drop_console: 删除console
  • compress.collapse_vars: 把定义一次的变量,直接使用,取消定义变量
  • compress.reduce_vars: 合并多次用到的值,定义成变量
  • 具体文档

优化webpack配置

webpack的优化,关系到打包出来文件的大小,打包的速度等,我们从以下几个方面对webpack进行优化:

1 优化打包速度

构建速度指的是我们每次修改代码之后热更新的速度和发布前打包文件的速度

1.1 合理的配置mode参数与devtool参数

mode 可设置development(开发)和production(生产)两个参数

如果没有设置,webpack4会将mode的默认值设置为production

production模式下会进行tree shaking(去除无用代码)和uglifyjs(代码压缩混淆)

1.2 缩小文件的搜索范围(配置include exclude alias noParse extensions

  • alias:当我们代码中出现import vue时,webpack会采用向上递归的方式去node_modules目录下去找。为了减少搜索范围我们可以直接告诉webpack去哪个路径下查找,也就是alias(别名)

  • include exclude: 同样配置include exclude 也可以减少webpack loader的搜索转换时间

  • noParse :当我们代码中用到import jq from 'jquery'时,webpack回去解析jq这个库是否有依赖的包。但是我们对类似jquery这类依赖库,一般会认为不会引用其他的包(特殊的除外),增加npParse属性告诉webpack不必解析,一次增加打包速度

  • extensionswebpack会根据extensions定义的后缀查找文件(频率高的文件类型优先写在前面)

    module.exports = {
     // 忽略其他配置
      module:{
      	noParse:/jquery/, // 不去解析jquery中的依赖库
        rules:[
          {
            test:/\.js$/,
            use:{
              loader:'babel-loader',
              options:{
                presets:['@babel/preset-env']
              }
            },
            exclude:/node_modules/
          },
          {
            test:/\.vue$/,
            use:[{
              loader:'vue-loader',
              options:{
                compilerOptions:{
                  preserveWhitespace:false
                }
              }
            }]
          },
          {
            test:/\.css$/,
            use:[{
              loader: devMode ? 'vue-style-loader' : MiniCssExtractPlugin.loader,
              options:{
                publicPath:"../dist/css/",
                hmr:devMode
              }
            },'css-loader',{
              loader:'postcss-loader',
              options:{
                plugins:[require('autoprefixer')]
              }
            }]
          },
          {
            test:/\.less$/,
            use:[{
              loader:devMode ? 'vue-style-loader' : MiniCssExtractPlugin.loader,
              options:{
                publicPath:"../dist/css/",
                hmr:devMode
              }
            },'css-loader','less-loader',{
              loader:'postcss-loader',
              options:{
                plugins:[require('autoprefixer')]
              }
            }]
          },
          {
            test:/\.(jep?g|png|gif)$/,
            use:{
              loader:'url-loader',
              options:{
                limit:10240,
                fallback:{
                  loader:'file-loader',
                  options:{
                    name:'img/[name].[hash:8].[ext]'
                  }
                }
              }
            }
          },
          {
            test:/\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
            use:{
              loader:'url-loader',
              options:{
                limit:10240,
                fallback:{
                  loader:'file-loader',
                  options:{
                    name:'media/[name].[hash:8].[ext]'
                  }
                }
              }
            }
          },
          {
            test:/\.(woff2?|eot|ttf|otf)(\?.*)?$/i,
            use:{
              loader:'url-loader',
              options:{
                limit:10240,
                fallback:{
                  loader:'file-loader',
                  options:{
                    name:'media/[name].[hash:8].[ext]'
                  }
                }
              }
            }
          }
        ]
      },
      resolve:{
        alias:{
          'vue$':'vue/dist/vue.runtime.esm.js',
          ' @':path.resolve(__dirname,'../src')
        },
        extensions:['*','.js','.json','.vue']
      },
    }

1.3 使用HappyPack开启多进程Loder转换

webpack构建过程中,实际上耗费时间大多数用在loader解析转换以及代码的压缩中。日常开发中我们需要使用loader对js,图片,字体等文件做转换操作,并且转换的文件数据量也是非常大的。由于js单线程的特性使得这些转换操作不能并发处理,而是需要一个个文件进行处理。HappyPack的基本原理是将这部分任务分解到多个子进程中并行处理,子进程处理完成之后把结果发送到主进程中,从而减少构建时间

安装

// npm 
npm i -D happypack
// yarn 
yarn add -D happypack

修改webpack.config.js配置

const HappyPack = require("happypack")
const os = require("os")
const happyThreadPool = HappyPack.ThreadPool({size:os.cpus().length})
module.exports = {
	// 忽略其他配置
	module:{
		rules:[
			{
				test:/\.js$/.
				use:[{
					loader:"happypack/loader?id=happyBabel"
				}],
				exclude:/node_modules/
			},
		],
		plugins:[
			new HappyPack({
				id:"happyBabel",
				loaders:[
					{
						loader:"babel-loader",
						options:{
							presets:[
								["@babel/preset-env"]
							],
							cacheDirectory: true
						}
					}
				],
				threadPool:happyThreadPool // 共享进程池
			})
		]
	}
}

1.4 使用webpack-parallel-uglify-plugin增强代码压缩

上面已经对loader转换进行了优化,还有一个难点就是优化代码的压缩时间

安装

// npm 
npm i -D webpack-parallel-uglify-plugin
// yarn 
yarn add -D webpack-parallel-uglify-plugin

修改webpack.config.js配置

const HappyPack = require("happypack")
const os = require("os")
const happyThreadPool = HappyPack.ThreadPool({size:os.cpus().length})
module.exports = {
	// 忽略其他配置
	optimization:{
		minimizer:[
			new ParallelUglifyPlugin({
				cacheDir:"./cache",
				uglifyJS:{
					output:{
						comments:false,
						beautify:false
					},
					compress:{
						drop_console:true,
						collapse_vars:true,
						reduce_vars:true
					}
				}
			})
		]
	}
}

1.5 提取第三方模块,DLL动态链接

对于开发项目中不经常会变更的静态依赖文件,因为很少会去变更,所以我们不希望这些依赖要被集成到内一次的构建逻辑中去。以后只要我们不升级第三方包的时候,那么webpack就不会对这些库去打包,这样可以快速的提高打包的速度。

这里我们使用webpack内置的DllPlugin DllReferencePlugin进行抽离 在与webpack配置文件同级目录下新建webpack.dll.config.js 代码如下:

// webpack.dll.config.js
const path = require("path");
const webpack = require("webpack");
module.exports = {
  // 你想要打包的模块的数组
  entry: {
    vendor: ['vue','element-ui'] 
  },
  output: {
    path: path.resolve(__dirname, 'static/js'), // 打包后文件输出的位置
    filename: '[name].dll.js',
    library: '[name]_library' 
     // 这里需要和webpack.DllPlugin中的`name: '[name]_library',`保持一致。
  },
  plugins: [
    new webpack.DllPlugin({
      path: path.resolve(__dirname, '[name]-manifest.json'),
      name: '[name]_library', 
      context: __dirname
    })
  ]
};

package.json中配置如下命令

"dll": "webpack --config build/webpack.dll.config.js"

接下来我们在webpack.config.js中增加配置

module.exports = {
  plugins: [
    new webpack.DllReferencePlugin({
      context: __dirname,
      manifest: require('./vendor-manifest.json')
    }),
    new CopyWebpackPlugin([ // 拷贝生成的文件到dist目录 这样每次不必手动去cv
      {from: 'static', to:'static'}
    ]),
  ]
};

执行npm run dll 或者 yarn run dll

之后会发现生成了我们需要的集合第三方代码的vendor.dll.js 我们需要在html文件中手动引入这个js文件

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
  <script src="static/js/vendor.dll.js"></script>
</head>
<body>
  <div id="app"></div>
</body>
</html>

这样如果我们没有更新第三方依赖包,就不必执行npm run dll 或者 yarn run dll,直接执行run dev 和 run build的时候就会发现我们的打包速度明显提升,因为我们已经通过dllPlugin将第三方依赖包进行了抽离

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published