webpack4核心概念的学习(二)

76 阅读5分钟

简单记录自己学习的点点滴滴, 一定要有产出,才会进步,有错误的地方还望大佬指出

webpack是什么?

webpack是javascript应用程序的静态模块打包器(Static Module bundle),在处理js应用程序时,会在内部形成一个依赖关系图,来映射项目中所用到的各个模块,并生成一个或者多个文件(bundle),以便供浏览器使用;

核心概念:

  • loader、plugins、Entry、Output、SourceMap、DevServer、Hmr、Babel
  • TreeShaking、环境区分、CodeSpilting、打包分析、代码压缩、环境变量的使用

上篇我们记录了loader、plugins、Entry、Output、SourceMap、DevServer、Hmr、Babel的学习,这篇我们开始学习TreeShaking、环境区分、CodeSpilting、打包分析、代码分割、环境变量的使用

1.TreeShaking

treeShaking就是把我们的代码中没有用到的代码给删除掉,减少打包的体积; webpack4之后,(mode:"production")模式是生产环境下,就默认开启了treeShaking,并不需要我们单独配置

2.环境区分

开发环境:

  • devServer
  • sourceMap
  • 接口代理,proxy
  • 热更新 HMR

生产环境:

  • treeShaking
  • 代码压缩
  • 提取公共代码

共同点:

  • 同样的入口
  • 部分相同的代码处理(loader,plugins)

方案:

  • webpack.dev.js 开发环境的

  • webpack.prod.js 生产环境的

  • webpack.base.js 开发环境和生产环境公用的代码

  • 借助一个工具: webpack-merge,来合并不同的webpack配置文件

    npm i webpack-merge -D
    

在根目录新建config文件夹,新建如下文件:

Snipaste_2022-10-23_18-23-54.png

首先配置webpack.base.js文件,这个配置文件是开发和生产都会用到的文件:

//nodejs中内置的path,来获取文件的路径
const path=require('path')
//根据目标html模板,自动生成html文件,并且会把打包后的js,css文件引入
const HtmlWebpackPlugin =require('html-webpack-plugin')
//清除每次打包生成的dist文件夹
const {CleanWebpackPlugin}=require('clean-webpack-plugin')
//将css单独提出去,并且以link的方式注入到html文件中的插件
const MiniCssExtractPlugin=require('mini-css-extract-plugin')
const Webpack= require('webpack')

module.exports={
    //不要写模式mode
    //入口文件
    entry:{
       index:'./src/index.js',
    },
    // 打包后输出的文件地址以及名称
    output:{
        filename:'[name].[hash:8].js',
        path:path.resolve(__dirname,'../dist')
    }
    module:{
        rules:[
           //处理js的loader
            {
                test:/\.js$/,
                loader:"babel-loader",
                exclude:/node_modules/
            },
            //处理图片的
            {
                test:/\.(png|jpeg|gif)$/,
                use:{
                    loader:'url-loader',
                    options:{
                        name:'[name].[hash:8].[ext]',
                        outputPath:'assets',
                        limit:2048,
                    }
                }
            },
            //处理字体的
            {
                test:/\.ttf$/,
                use:{
                    loader:'file-loader',
                    options:{
                        name:'[name].[hash:8].[ext]',
                        outputPath:'font'
                    }
                }
            },
            //处理样式css的
             {
            test:/\.css$/,  //匹配css文件
            use:[      //直接使用loader,垂直方向写执行的顺序是从下到上; ['style-loader','css-loader','postcss-loader']-->从右往左执行          
                MiniCssExtractPlugin.loader,
                'css-loader',
                {  
                    loader:"postcss-loader",
                    options:{
                       plugins:[require('autoprefixer')]
                    }
                }
            ]
        },
        ]
    },
    plugins:[
        new HtmlWebpackPlugin({
            template:"./src/index.html"
        }),
        new CleanWebpackPlugin(),
        new MiniCssExtractPlugin({  //可以不配置,可以指定打包输出css的文件名
            filename:'[name].css'
        }),
    ],
}

webpack.dev.js

const path=require('path')
const Webpack=require('webpack')
//合并webpack配置文件的插件
const {merge}=require('webpack-merge')
//引入基础的配置
const baseConfig=require('./webpack.base')
const devConfig={
    mode:"development",  //开发环境
    devtool:'cheap-module-eval-source-map',  //开启sourceMap,定位代码错误位置
    plugins:[
    //开启热更新的插件
        new Webpack.HotModuleReplacementPlugin()
    ],
    //使用webpack-dev-server作为服务器启动
    devServer:{
        contentBase:path.join(__dirname,'dist'),
        port:9000,  //端口号
        hot:true   //开启热更新
    }

}
module.exports=merge(baseConfig,devConfig) 

webpack.prod.js

const {merge}=require('webpack-merge')
const baseConfig=require('./webpack.base')
const prodConfig={
    mode:"production",  //生产环境
    devtool:'none'//不开启sourceMap
}
module.exports=merge(baseConfig,prodConfig)

需要配置下package.json的文件

 ......省略代码
 "scripts": {
    //开发环境配置的打包命令
    "build:dev": "webpack --config ./config/webpack.dev.js",
    //生产环境配置的打包命令
    "build:prod": "webpack --config ./config/webpack.prod.js",
    //开发环境的启动服务的命令
    "dev": "webpack-dev-server --config ./config/webpack.dev.js"
  },
  ......省略代码

3.CodeSpilting(代码分割)

代码分割是把公共资源拆分出来的手段来提升打包的速度和项目的性

一般是三种方式来进行

  1. 多入口配置:entry多入口 一般是配合 webpack.proviePlugin 使用
  2. 抽取公共代码: splitchunksPlugins
  3. 动态加载:按需加载,懒加载 @babel/plugin-syntax-dynamic-import使用

1.多入口实现代码分割,我们安装jquery来学习

   npm  i jquery 
index.js文件代码
    import jquery from 'jquery'  //引入jquery
    console.log('index.js')

先不配置,我们打包后看代码的体积:执行npm run build:prod 命令,我们可以看到代码:89kb

Snipaste_2022-10-23_21-34-09.png

配置多入口在webpack.base.js文件中:

  ...
  // 打包后输出的文件地址以及名称
    output:{
        filename:'[name].[hash:8].js',
        path:path.resolve(__dirname,'../dist')
    },
  ...
  
   plugins:[
        new Webpack.ProvidePlugin({
            $:"jquery"  //配置jquery使用的别名
        })
    ],

再次打包:可以看到index.js文件只有不到1kb,jquery已经被提取出去了

Snipaste_2022-10-23_21-37-24.png

2.抽取公共代码,需要使用

index.js文件代码
    import jquery from 'jquery'
    console.log('index.js')

webpack.config.js中配置:

module.exports={
    .....
    //配置splitchunks  分割代码
    optimization:{
       splitChunks:{
          chunks:'all'
       }
    },
   .....
   
   详细版配置
    optimization:{
       splitChunks:{
        chunks: 'all',  //initial async all 三个值选择
        minSize: 30000,  //拆分代码的最小大小,默认是30000
        maxSize: 0,   //拆分前代码最大是多大
        minChunks: 1, //表示被引用的次数,默认是1
        maxAsyncRequests: 5,  //最大的按需异步加载次数
        maxInitialRequests: 3,  //最大的初始化加载次数
        automaticNameDelimiter: '~', //生成文件名中的分割符,默认是~
        automaticNameMaxLength: 30,
        name: true,  //true 表示用自动生成的文件名命名
        name:false,
        cacheGroups: {
          vendors: {
            test: /[\\/]node_modules[\\/]/,  //匹配node_modules目录下的文件
            priority: -10, //优先级
            filename:"jquery.js"  //自定义打包后的名字
          },
          default: {
            minChunks: 2,
            priority: -20,
            reuseExistingChunk: true  //是指模块被打包过了,就不用再打包了,复用之前的
          }
       }
    }
}

打包后:可以看到jquery也是被提取出去的

Snipaste_2022-10-23_22-06-16.png

3.按需加载:动态懒加载

index.js文件代码
    console.log('index.js')
    // 动态懒加载的方式
    import(/*webpackChunkName:'jquery'*/ 'jquery').then(({default:$})=>{
        console.log($.length)
    })

npm run build:prod 打包,可以看到jquery也是可以被单独提取出去的

Snipaste_2022-10-23_21-45-59.png

有的版本的问题,可能需要下载个异步插件来配合使用

    npm i @babel/plugin-syntax-dynamic-import -D

并且需要在.babelrc文件中配置下:

{
    "plugins":[
        "@babel/plugin-syntax-dynamic-import",  //配置插件
        [
            "@babel/plugin-transform-runtime",
            {
                "corejs":3
            }
        ]
    ]
}

4.代码压缩

css的代码压缩:需要下面的插件来帮助

  npm i optimize-css-assets-webpack-plugin -D

没有配置插件的时候,打包出来的css代码:

body{
    background-color: red;
    div{
        color:yellow;
    }
}

配置插件:webpack.prod.js中配置:

....
//引入插件:
const OptimizeCssAssetsWebpackPlugin=require('optimize-css-assets-webpack-plugin')
const prodConfig={
     ....
     //配置使用插件
    optimization:{
        minimizer:[new OptimizeCssAssetsWebpackPlugin()]
    }
}

此时打包后的css文件代码是:

body{background-color:red}body div{color:#ff0}

配置完css的代码压缩,我们打开打包后的js文件,发现没有被压缩;因为我们在webpack.prod.js中配置了optimization,会把默认的mode:"production"下压缩js的规则给覆盖掉,需要我们手动添加下:

npm i terser-webpack-plugin -D

webpack.prod.js配置如下:

const {merge}=require('webpack-merge')
const baseConfig=require('./webpack.base')
const OptimizeCssAssetsWebpackPlugin=require('optimize-css-assets-webpack-plugin')
const TerserWebpackPlugin=require('terser-webpack-plugin')  //引入压缩js的插件
const prodConfig={
    mode:"production",
    devtool:'none',
    optimization:{
        minimizer:[new OptimizeCssAssetsWebpackPlugin(),new TerserWebpackPlugin()]
    }
}
module.exports=merge(baseConfig,prodConfig)

5.代码分析

我们打包后,想要直观的查看各个包的大小,就需要一个插件webpack-bundle-analyzer来帮忙

 npm i webpack-bundle-analyzer -D
 

在webpack.prod.js中配置如下:

 .....
const BundlePluginAnalyzer=require('webpack-bundle-analyzer').BundleAnalyzerPlugin
const prodConfig={
    ...
    optimization:{
        minimizer:[new BundlePluginAnalyzer()]
    }
}
.....

npm run build:prod 打包后会自己打开网页,可以直观的看到包的大小,有助于我们优化

Snipaste_2022-10-23_22-44-27.png

6.环境变量的使用

开发环境(development)

开发环境是程序猿们专门用于开发的服务器,配置可以比较随意,为了开发调试方便,一般打开全部错误报告。(程序员接到需求后,开始写代码,开发,运行程序,看看程序有没有达到预期的功能;)

测试环境(testing)

一般是克隆一份生产环境的配置,一个程序在测试环境工作不正常,那么肯定不能把它发布到生产机上。(程序员开发完成后,交给测试部门全面的测试,看看所实现的功能有没有bug,测试人员会模拟各种操作情况;)

生产环境(production)

生产环境是指正式提供对外服务的,一般会关掉错误报告,打开错误日志。(就是线上环境,发布到对外环境上,正式提供给客户使用的环境。)

三个环境也可以说是系统开发的三个阶段:开发->测试->上线,其中生产环境也就是通常说的真实环境

1.我们先使用cross-env来帮助我们区分环境

   npm i cross-env -D

然后修改package.json中的打包命令:

"scripts": {
    //  开发环境打包命令
    "build:dev": "cross-env NODE_ENV=development webpack --config ./config/webpack.dev.js",
    //  生产环境打包命令
    "build:prod": "cross-env NODE_ENV=production webpack --config ./config/webpack.prod.js",
    "dev": "webpack-dev-server --config ./config/webpack.dev.js"
  },

我们在webpack.prod.js中,添加如下代码:

    const NODE_ENV=process.env.NODE_ENV;
    console.log('这个是什么环境呢',NODE_ENV);

npm run build:prod 执行打包命令:可以看到打印出的是production生产环境,同理,开发环境也是一样的

Snipaste_2022-10-23_23-00-54.png

2.我们使用yargs这个插件来帮助我们获取环境

  npm i yargs -D
  

修改package.json中的打包命令:

 "scripts": {
    "build:dev": "webpack --config ./config/webpack.dev.js --env development",
    "build:prod": "webpack --config ./config/webpack.prod.js --env production",
    "dev": "webpack-dev-server --config ./config/webpack.dev.js"
  },

我们在webpack.prod.js中,添加如下代码:

   const argv=require('yargs').argv
   console.log('这个是什么环境呢',argv);

npm run build:prod 执行打包命令:可以看到打印出的是对象,argv.env就是代表的当前环境

Snipaste_2022-10-23_23-07-51.png

区分环境之后,我们在webpack.base.js文件中做一些不同的配置,比如:你想要把生产环境与开发环境打包输出的js文件名不一样,配置如下:

webpack.base.js

     ......
    // 获取环境变量
    const argv=require('yargs').argv
    const moduleFlag=argv.env==='production'?true:false
    ......
      // 打包后输出的文件地址以及名称
    output:{
        filename:moduleFlag?'[name].[hash:8].js':'bundle.js',
        path:path.resolve(__dirname,'../dist')
    },
    ......

执行打包命令:npm run build:dev 打包后的js文件名:bundle.js

Snipaste_2022-10-23_23-18-46.png

执行打包命令:npm run build:prod 打包后的js文件名:index.41bca881.js

Snipaste_2022-10-23_23-17-59.png

最终的配置文件代码如下: webpack.base.js

//nodejs中内置的path,来获取文件的路径
const path=require('path')
//根据目标html模板,自动生成html文件,并且会把打包后的js,css文件引入
const HtmlWebpackPlugin =require('html-webpack-plugin')
//清除每次打包生成的dist文件夹
const {CleanWebpackPlugin}=require('clean-webpack-plugin')
//将css单独提出去,并且以link的方式注入到html文件中的插件
const MiniCssExtractPlugin=require('mini-css-extract-plugin')
const Webpack= require('webpack')

// 获取环境变量
const argv=require('yargs').argv
const moduleFlag=argv.env==='production'?true:false

module.exports={
    //不要写模式mode
    //入口文件
    entry:{
       index:'./src/index.js',
    //    jquery:"jquery"
    },
    // 打包后输出的文件地址以及名称
    output:{
        filename:moduleFlag?'[name].[hash:8].js':'bundle.js',
        path:path.resolve(__dirname,'../dist')
    },
    optimization:{
       splitChunks:{
        chunks: 'all',  //initial async all 三个值选择
        // minSize: 30000,  //拆分代码的最小大小,默认是30000
        // maxSize: 0,   //拆分前代码最大是多大
        // minChunks: 1, //表示被引用的次数,默认是1
        // maxAsyncRequests: 5,  //最大的按需异步加载次数
        // maxInitialRequests: 3,  //最大的初始化加载次数
        // automaticNameDelimiter: '~', //生成文件名中的分割符,默认是~
        // automaticNameMaxLength: 30,
        // name: true,  //true 表示用自动生成的文件名命名
        name:false,
        cacheGroups: {
          vendors: {
            test: /[\\/]node_modules[\\/]/,  //匹配node_modules目录下的文件
            priority: -10, //优先级
            filename:"jquery.js"  //自定义打包后的名字
          },
          default: {
            minChunks: 2,
            priority: -20,
            reuseExistingChunk: true  //是指模块被打包过了,就不用再打包了,复用之前的
          }
       }
    }
},
    module:{
        rules:[
           //处理js的loader
            {
                test:/\.js$/,
                loader:"babel-loader",
                exclude:/node_modules/
            },
            //处理图片的
            {
                test:/\.(png|jpeg|gif)$/,
                use:{
                    loader:'url-loader',
                    options:{
                        name:'[name].[hash:8].[ext]',
                        outputPath:'assets',
                        limit:2048,
                    }
                }
            },
            //处理字体的
            {
                test:/\.ttf$/,
                use:{
                    loader:'file-loader',
                    options:{
                        name:'[name].[hash:8].[ext]',
                        outputPath:'font'
                    }
                }
            },
            //处理样式css的
             {
            test:/\.css$/,  //匹配css文件
            use:[      //直接使用loader,垂直方向写执行的顺序是从下到上; ['style-loader','css-loader','postcss-loader']-->从右往左执行          
                MiniCssExtractPlugin.loader,
                'css-loader',
                {  
                    loader:"postcss-loader",
                    options:{
                       plugins:[require('autoprefixer')]
                    }
                }
            ]
        },
        {
            test:/\.less$/,
            use:[MiniCssExtractPlugin.loader,'css-loader','less-loader','postcss-loader']
        }
        ]
    },
    plugins:[
        new HtmlWebpackPlugin({
            template:"./src/index.html"
        }),
        new CleanWebpackPlugin(),
        new MiniCssExtractPlugin({  //可以不配置,可以指定打包输出css的文件名
            filename:'[name].css'
        }),
        // new Webpack.ProvidePlugin({
        //     $:"jquery"
        // })
    ],
}

webpack.dev.js

const path=require('path')
const Webpack=require('webpack')
const {merge}=require('webpack-merge')
const baseConfig=require('./webpack.base')
const devConfig={
    mode:"development",
    devtool:'cheap-module-eval-source-map',
    plugins:[
        new Webpack.HotModuleReplacementPlugin()
    ],
    devServer:{
        contentBase:path.join(__dirname,'dist'),
        port:9000,
        hot:true 
    }

}

module.exports=merge(baseConfig,devConfig)

webpack.prod.js

const {merge}=require('webpack-merge')
const baseConfig=require('./webpack.base')
const OptimizeCssAssetsWebpackPlugin=require('optimize-css-assets-webpack-plugin')
const TerserWebpackPlugin=require('terser-webpack-plugin')
// const BundlePluginAnalyzer=require('webpack-bundle-analyzer').BundleAnalyzerPlugin

// 使用cross-env来获取环境变量
// const NODE_ENV=process.env.NODE_ENV;
// console.log('这个是什么环境呢',NODE_ENV);

// 使用yargs来获取环境变量
const argv=require('yargs').argv
console.log('这个是什么环境呢',argv);

const prodConfig={
    mode:"production",
    devtool:'none',
    optimization:{
        minimizer:[new OptimizeCssAssetsWebpackPlugin(),
            new TerserWebpackPlugin(),
            //new BundlePluginAnalyzer()
        ]
    }
}
module.exports=merge(baseConfig,prodConfig)

最终的package.json对应的版本,以便后续的学习

{
  "name": "webpack-demo",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "build:dev": "webpack --config ./config/webpack.dev.js --env development",
    "build:prod": "webpack --config ./config/webpack.prod.js --env production",
    "dev": "webpack-dev-server --config ./config/webpack.dev.js"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@babel/core": "^7.19.6",
    "@babel/plugin-syntax-dynamic-import": "^7.8.3",
    "@babel/plugin-transform-runtime": "^7.19.6",
    "@babel/polyfill": "^7.12.1",
    "@babel/preset-env": "^7.19.4",
    "@babel/runtime-corejs3": "^7.19.6",
    "autoprefixer": "^9.7.2",
    "babel-loader": "^8.2.5",
    "clean-webpack-plugin": "^4.0.0",
    "core-js": "^3.25.5",
    "cross-env": "^7.0.3",
    "css-loader": "^5.2.7",
    "file-loader": "^6.2.0",
    "html-webpack-plugin": "^4.0.0",
    "less-loader": "^7.3.0",
    "mini-css-extract-plugin": "^1.6.2",
    "optimize-css-assets-webpack-plugin": "^6.0.1",
    "postcss-loader": "^3.0.0",
    "sass-loader": "^7.3.1",
    "style-loader": "^2.0.0",
    "terser-webpack-plugin": "^4.1.0",
    "url-loader": "^4.1.1",
    "webpack": "^4.16.1",
    "webpack-bundle-analyzer": "^4.6.1",
    "webpack-cli": "^3.3.12",
    "webpack-dev-server": "^3.11.2",
    "webpack-merge": "^5.8.0",
    "yargs": "^17.6.0"
  },
  "dependencies": {
    "jquery": "^3.6.1",
    "lodash-es": "^4.17.21"
  }
}

以上就是webpack核心概念的学习,有不对的地方还望大佬指出,加油,学习前端每一天!!!