webpack学习

87 阅读9分钟

webpack

webpack 是一个用于现代 JavaScript 应用程序的 静态模块打包工具。当 webpack 处理应用程序时,它会在内部从一个或多个入口点构建一个 依赖图(dependency graph),然后将你项目中所需的每一个模块组合成一个或多个 bundles,它们均为静态资源,用于展示你的内容。 一个简单的webpack配置由五部分组成:入口,出口,loader,plugin,model

入口

入口表示一个webpack程序 该重那个文件开始执行,一般分为单入口和多入口。

//单入口
module.exports={
entry:'index.js'
}
modules.exports={
entry:['index.js','index1.js']
}
//多入口
//多入口就对多出口,用于分离自身引用和第三方应用,有利于快速的提高项目的性能
module.exports={
 entry:{
 main:'index.js',
 others:"other.js"
 },
 output:{
 filename:'[name].[hash:6].js',//多出口
 path:path.resolve(__dirname__,'dist')
 }

}
出口

出口就是通过webpack打包后生成的文件,我们可以通过配置,去设置输出文件的名称结构,文件地址等

module.exports={
 entry:{
 main:'index.js',
 others:"other.js"
 },
 output:{
 filename:'[name].[hash:6].js',//多出口
 path:path.resolve(__dirname__,'dist') 
 
 }

}
loader

loader可以对于程序中的源代码进行转换,将浏览器或者webpack不能识别的文件类型或者语言转换成可以识别的文件,比如es6语法,less,scss语法,图片转换等。 常见的loader使用:

  1. css-loader、style-loader
module.export={
entry:'index.js',
output:{
 filename:"index.js",
 path:path.resolve(__dirname__,'dist')
},
module:{
  rules:[
     {test:/\.css/,
       use:['style-loader' ,/解析js中的css,通过创建sytle标签的形式去 插入到html中
         'css-loader',//用户将css解析到js中,还可以通过css-loader的配置去开启css的
         //模块化
        //{
        //loader:"css-loader",
        //options:{
          //module:true
         //}
         //}
         ]
         
       }
     ]
    }


}
  1. 使用scss-loader,less-loader
//scss-loader
  module.exports={
    module:{
        rules:[
        {test:/\.scss/,use:[
         'style-loader','css-loader','scss-loader'  
        ]}
        ]
    }
  }
  //less-loader
    module.exports={
    module:{
        rules:[
        {test:/\.less/,use:[
         'style-loader','css-loader','less-loader'  
        ]}
        ]
    }
  }
  1. 使用postcss-loader
module.exports={
module:{
rules:[
   rules:[
        {test:/\.css/,use:[
         'style-loader','css-loader','postcss-loader'  
         
        ]}
        ]
]
}

}

plugin

可以对于webpack中的功能进行扩展,即loader不能满足开发需求的时候使用。创建的plugin有: HTMLWebpackPlugin,LimitChunkCountPlugin,HotModuleReplacementPlugin,cleanwebpackPlugin,copyWebpackPlugin,MinicssExtractWbebpackPlugin,OptimizeCSSPlugin,CompressionWebpackPlugin

  1. webpackPlugin
const htmlwebpackplugin= require('html-webpack-plugin')
const {cleanWebpackPlugin} = require('clean-webpack-plugin')
modules.export={
plugins:[
new cleanWebpackPlugin(),
new HtmlwebpackPlugin({
filename:"index.html", //生成的html的名称
template:"./src/index.html",用哪个模板shegnchegnhtml
})
]
}

2.静态资源的拷贝(copy-webpack-plugin)

plugins:[
new CopyWebpackPlugin({
patterns:[{from:''移动的文件地址'',to:''移动的目标地址""},]
})
]

3.webpack的内置设置全局变量的插件(ProvidePlugin)

const webpack = require('webpack')
plugins:[
new webpack.ProvidePlugin({
vue:'vue',
_map:['loadsh','map'] //取单个属性,第一参数是module 第二是propoty
})

]

4.css样式抽离

const miniCssExarctPlugin=require('mini-css-exarct-plugin')
module:{
rules:[
test:/\.css$/,
use:[miniCssExarctPlugin.loader,'css-loader',{loader:"postcss-loader",options:{plugins:function(){return [require('autoprefixer')({overrideBrowserslist}:['defaults'])]}}}]
]

}

plugins:[new miniCssExarctPlugin({filename:"css/[name].css"})]

5.css样式压缩

const OptimizeCssPlugin = require('optimize-css-assets-webpack-plugin');

module.exports = {
   entry: './src/index.js',
   //....
   plugins: [
       new OptimizeCssPlugin()
   ],
}

6.多入口打包

export.modules={
entry:{
index:"./index.js",
login:"./login.js"
},
output:{
filename:"[name].[hash:8].js",
path:path.resolve(__dirname,'dist')
},
plugins:[
new HtmlWebpackPlugin({
       template:"./index.html", //打包的模板
       filename:"index.html" , //打包后的文件名称
       chunks:['index'] //指定打包后的html加载那个打包后的文件 ,不指定则全部加载
   }),
   new HtmlWebpackPlugin({
       template:"./login.html", //打包的模板
       filename:"login.html",  //打包后的文件名称
       chunks:['login']
   }),
]
}

7.定义环境变量(webpack.DefinePlugin)

 new webpack.DefinePlugin({
       '_DEV': 'dev' , //字符串
       '_FLAG': 'true' //FLAG 是个布尔类型
   }),
   //index.js
  if(_DEV=='dev'){
  //逻辑
  }

8.html压缩

 new HtmlWebpackPlugin({
       template:"./index.html", //打包的模板
       filename:"index.html" , //打包后的文件名称
      minify:{
      removeComments:true//移除html中的注释
      collapseWhitespace:true //移除空白
      minifycss:true //压缩内联css
      }
   }),

9.js压缩(webpack5自带)

webpack4
// 引入 ParallelUglifyPlugin 插件
const ParallelUglifyPlugin = require('webpack-parallel-uglify-plugin');

module.exports = {
 plugins: [
   // 使用 ParallelUglifyPlugin 并行压缩输出JS代码
   new ParallelUglifyPlugin({
     // 传递给 UglifyJS的参数如下:
     uglifyJS: {
       output: {
         /*
          是否输出可读性较强的代码,即会保留空格和制表符,默认为输出,为了达到更好的压缩效果,
          可以设置为false
         */
         beautify: false,
         /*
          是否保留代码中的注释,默认为保留,为了达到更好的压缩效果,可以设置为false
         */
         comments: false
       },
       compress: {
         /*
          是否在UglifyJS删除没有用到的代码时输出警告信息,默认为输出,可以设置为false关闭这些作用
          不大的警告
         */
         warnings: false,

         /*
          是否删除代码中所有的console语句,默认为不删除,开启后,会删除所有的console语句
         */
         drop_console: true,

         /*
          是否内嵌虽然已经定义了,但是只用到一次的变量,比如将 var x = 1; y = x, 转换成 y = 5, 默认为不
          转换,为了达到更好的压缩效果,可以设置为false
         */
         collapse_vars: true,

         /*
          是否提取出现了多次但是没有定义成变量去引用的静态值,比如将 x = 'xxx'; y = 'xxx'  转换成
          var a = 'xxxx'; x = a; y = a; 默认为不转换,为了达到更好的压缩效果,可以设置为false
         */
         reduce_vars: true
       }
     }
   }),
 ]
}

ParallelUglifyPlugin插件还有其他属性:

test:  使用正则去匹配哪些文件需要被 ParallelUglifyPlugin 压缩,默认是 /.js$/.
include:  使用正则去包含被 ParallelUglifyPlugin 压缩的文件,默认为 [].
exclude:  使用正则去不包含被 ParallelUglifyPlugin 压缩的文件,默认为 [].
cacheDir:  缓存压缩后的结果,下次遇到一样的输入时直接从缓存中获取压缩后的结果并返回,cacheDir 用于配置缓存存放的目录路径。默认不会缓存,想开启缓存请设置一个目录路径。

workerCount: 开启几个子进程去并发的执行压缩。默认是当前运行电脑的 CPU 核数减去1。
sourceMap: 是否为压缩后的代码生成对应的Source Map, 默认不生成,开启后耗时会大大增加,一般不会将压缩后的代码的
sourceMap发送给网站用户的浏览器。

devtool

devtool是将我们源码和打包后的代码做一个映射,devtool有如下几种值

  1. eval:速度最快,使用eval包裹模块代码,会显示对应源文件的行数
  2. souce-map :产生.map的文件。 会显示行列信息
  3. hidden-source-map:和souce-map类型 但是不会和源码关联 只显示打包后输出信息所有行数
  4. cheap:较快不包含列信息
  5. module:第三方的模块 包含loader中的sourcemap(jsx babel的sourecemap)
  6. inline:将.map作为dataUrl嵌入到打包后的文件, 不会单独的生成.map文件 造成bundle的文件过大 会显示输出信息所在行数 这几种值我们也可以组合使用
//开发环境建议
devtool:cheap-module-eval-sourece-map//souce-map
//生产环境不建议开启
devServer

我们可以通过webpack-dev-server 创建的小型服务,通过小型服务可以去访问我们的项目页面。

  1. 我们通过npx webpack server 去开启服务
//让服务运行在9000端口 同时打开浏览器 
npx webpack server --port 9000 --open
  1. 通过webpack.config.js 中的devServer 进行设置
module.exports={
devServer:{
post:9000,
open:true,
compress:true//开启压缩
}
}

resolve

resolve中我们可以配置我路径的别名,第三方模块的查找路径 以及我们引入的文件从那些后缀开始解析。

export.modules={
resolve:{
 module:[],//配置第三方模块的查找地址,默认node_module
 alias:{//配置别名
  '@':'./src'
 },
 extensions:['.web.js','.wx.js','js'] //导入文件的时候可以省略后缀,程序自动查找 按照extensions 的顺序查找。
}
}

其他指令参考webpack-dev-server官网

webpack中的性能优化

1.speed-measure-webpack-plugin 插件可以查看打包时各个插件loader的执行时间

const SpeedMeasurePlugin=require('speed-measure-webpack-plugin')
const smp=new SpeedMeasurePlugin()
const config={
entry:{},
output:{},
module:{},
plugins:{}
}
module.expores=smp.warp(config)
//打包的时候可以看到 各个插件和loader的时间

2.exclude和include exclude的优先级比include的优先级高。但是在使用的时候建议使用include 减少转译时候文件资源的加载数量

module:{
rules:[{
test:/.jsx/,
use:['babel-loader'],
include:[path.reslove(__dirname,'src')]
}]
}

3.cache-loader 通过cache-loader 将资源加载到磁盘 后续构建的时候直接从磁盘中读取,添加在要缓存loader的前面

module:{
rules:[{
test:/.jsx/,
use:['cache-loader','babel-loader'],
include:[path.reslove(__dirname,'src')]
}]
}

4.happypack(项目复杂的时候使用) 通过happypack的方式开启webpack同一时间节点开启多个任务提高 构建速度

const Happypack = require('happypack');
module:{
rules:[{
test:/.jsx/,
 use: 'Happypack/loader?id=js',//id是唯一的标识
include:[path.reslove(__dirname,'src')]
},{
test:/.css/
 use: 'Happypack/loader?id=css',//id是唯一的标识
}]
}
plugins:[
new Happypack({
  id: 'js', //和rule中的id=js对应
            //将之前 rule 中的 loader 在此配置
            use: ['babel-loader'] //必须是数组
​
})
new Happypack({
  id: 'css', //和rule中的id=css对应
            //将之前 rule 中的 loader 在此配置
            use: ['style-loader','css-loader','postcss-loader'] //必须是数组
​
})
]
//注意如果css中加载了postcss-loader的时候 则要在项目根路径下添加postcss.config.js
//postcss.config.js
module.exports = {
    plugins: [
        require('autoprefixer')()
    ]
}

5.thread-loader(开启单独的工作池,和happypack类似) thread-loader的使用是放在要被优化的loader之前,优化的loader不能产生新文件,不能使用定制的loader api 优化的loader无法获取webpack的选项

npm i thread-loader -D
module:{
rules:[{
test:/.jsx/,
use:[''thread-loader','cache-loader','babel-loader'],
include:[path.reslove(__dirname,'src')]
}]
}

6.HardSourceWebpackPlugin(中间缓存) 首次构建时间不变后续构建时间提高80%

//webpack.config.js
var HardSourceWebpackPlugin = require('hard-source-webpack-plugin');
module.exports = {
    //...
    plugins: [
        new HardSourceWebpackPlugin()
    ]
}
​

7.noParse module.noParse 可以是的我们将不需要转换和解析的第三方模块(该模块内部没有amd规范comnand)进行排除,这样提高了webpack的构建速度

module.exports={
module:{
noParse:/vue|loadsh/
}
}

8.externals (通过cdn手动引入,通过import 在程序中引入(得下载对应的依赖)但是不走webpack的打包,也就是我们可以正常的去编码,后续打包的时候不讲我们使用的依赖打包 直接使用cdn)

<html>
<head>
</head>
<body>
<script src='http://libs.baidu.com/jquery/2.0.0/jquery.min.js'></script>
</body>
</html>
module.exports={
externals:{
//cdn引入后全局有个jQuery
jquery:'jQuery'
}
}

8.抽离公共公共代码 optimization.splitChunks

module.exports={
 optimization:{
 concatenateModules: false,
    splitChunks: {//分割代码块
      maxInitialRequests:6, //默认是5
      cacheGroups: {
        vendor: {
          //webpack默认第三方依赖 进vendor缓存组 
          priority: 1,
          name: 'vendor',
          test: /node_modules/,
          chunks: 'initial',
          minSize: 100,
          minChunks: 1 //重复引入了几次
        },
        'lottie-web': { //自定义缓存组
          name: "lottie-web", // 单独将 react-lottie 拆包
          priority: 5, // 权重需大于`vendor`
          test: /[/]node_modules[/]lottie-web[/]/, //分割代码的地址
          chunks: 'initial', //
          minSize: 100,
          minChunks: 1 //重复引入了几次
        },
        default:{ //默认缓存组,防止重复的代码
        
        }
      }
 }
​
​
}

9.自身的tree-shaking

babel7

babel是代码解析和语法转换的工具 可以将es的高级语法转成向后兼容的语法,以适配当前的浏览器或者旧版本的浏览器。 babel的作用:1.语法转换,2.ployfill 提供不同浏览器对于es6语法的缺失的环境,3源码转换 使用babel的时候就下载@babel/core 这是babel的核心库 @babel/cli 为babel提供了babel的命令 可以再cli中使用babel这个命令。 通过预设或者插件的方式让babel的能加完善或者满足的我们实际的开发需求.babel的配置文件为.babelrc

{
“presets”:['@babel/preset-env'],
plugins:['@babel/plugin-transform-runtime']
}
//browerslistrc   添加我们适配的浏览器 超过90%的用户使用的浏览器
>0.25%
not dead
​

@babel/preset-env 是对于我们使用的浏览器中缺失的功能进行polyfill 使得我们的浏览器很好的对于高级的语法的支持 @babel/polyfill 则是为不识别转换后的高级语法 提供识别环境(但是会造成污染全局环境)

//webpack入口添加  或者再入口js中引用
entry:[
require.solve('./polyfills'),
path.resove(./index.js)
]
//polyfills
import '@/babel-polyfill‘  //这种引入的方式会造成 将所有的polyfill的包都引进来浪费资源
​

polyfill/按需引入

//.babelrc
{
“presets”:[['@babel/preset-env',{
  "useBuiltIns":"usage", //使用的时候  babel检查代码按需引入,这种方式也需要下载@babel/polyfill
  "corejs":3
​
}]],

上面的方式 已经很好的解决了 老旧版本浏览器对于es6语法的支持,以及 浏览器环境缺失高级语法转换环境的环境补充,以及解析后输出文件体积过大的问题,但是,现在还有一个问题就是 我们代码中使用了多个一样的高级语法 就会出现inject多次,比如 我们使用了10此次 class 则再编译转换的时候 会inject10 这显然是不合适的 @/babel/plugin-transform-runtime(开发)(@babel/runtime 生产) 解决了这个问题,可以重复使用注入的帮助程序

//.babelrc
{
"presets":[['@babel/preset-env'],{
 "useBuiltIns":'usage', //按需引入
 "corejs":3
}],
plugins:[
["@babel/plugin-transform-runtime",{
"corejs":3
}],"@/babel-plugin-react"  //解析转换jsx语法
]
}
​
//插件和预设
插件在预设之前,插件的执行是从前往后,预设是从后往前

代码分隔和异步加载补充

代码分隔分为

  1. 通过入口
entry:{
  index:"./index.js"about:"./aboout.js"
},
output:{
filename:"[name].js",
path:path.resolve(paht.__dirname__,'dist')
}

2.防止重复

entry:{
  index:"./index.js",

},
output:{
filename:"[name].js",
path:path.resolve(paht.__dirname__,'dist')
},
opizization:{
 splitChunks:{
  chunks:'all'
 }
}

vue组件中的异步路由和加载方式

我们通过import的方式去加载动态路由  点击对应的路由会加载对应的chunks
webpack中的配置
outpath:{
path:path.resolve(__dirname__,'dist')
filename:"[name].js"
chunkFilename:"[name].js"
},
optimization: {
      splitChunks: {
        chunks: 'all',
       },
    
   },