webpack

1.webpack的理解

1.为什么要用到webpack

传统方式开发项目的不足:
1)项目的文件非常多,文件起一个名字叫模块。
2)有些模块需要手动的编译 less css
3)模块特别多,放到服务器上,肯定会有非常多的二次请求
4)模块和模块之是有依赖关系

2.打包工具

grunt
glup
Rollup 是一个 JavaScript 模块打包器
webpack 大 笨重 可以打包很多不同类型的模块
vite

3.webpack的优势

1)基于Node开发的 webpack配置中的写法,是Node的写法
2)webpack打包非常多类型的模块 js
3)解决模块之是的依赖关系 减少模块之间的依赖

2.webpack打包

1.安装webpack

  1. 本地安装:npm i webpack webpack-cli@3.3.12 //处理css兼容的loader与@4版本不兼容
  2. 全局安装:npm i webpack webpack-cli -g

注:npx

  1. 你要使用webpack命令,你必须全局安装webpack
  2. 如果你不想全局安装,那么就可以使用npx
  1. npx 默认会临时全局安装webpack

2.打包

  1. npx webpack ./src/index.js -o ./dist/main.js
  2. 如果全局安装了,就不需要加npx 如果没有全局安装,需要加上npx

默认情况下,webpack的功能也是比较弱的,只能处理低级的JS语法和json文件。
其它模块,webpack默认是不能处理的。

3.打包模式

开发模式打包 和 生产模式打包: 默认是生产模式打包

  1. 开发模式打包: npx webpack ./src/index.js -o ./dist/main.js --mode=development
  1. 生产模式打包: npx webpack ./src/index.js -o ./dist/main.js --mode=production

注: 生产模式打包比开发模式打包多了一个代码压缩。

3.配置webpack

上面的打包方式是零配置打包,零配置打包还是比较弱,一般情况下我们自己配置webpack。

创建webpack-.config.js文件,用于写配置代码

1.配置入口和出口

  1. const path = require("path");
  2. module.exports = {
  3. mode:"development", //开发者模式
  4. // 入口
  5. entry:"./src/index.js", //用于打包的入口文件
  6. // 出口
  7. output:{
  8. filename:"main.js", //出口文件的名字
  9. path:path.resolve(__dirname,"dist") //出口目录
  10. }
  11. }

2.配置css的load

css-loader:把css文件转成commonjs模块,加载到JS模块<br />
style-loader: 创建一个style标签,把样式资源添加页面的头部中
npm i style-loader css-loader -D

js

// 配置loader
    module:{
        // 规则
        rules:[
            {
                test:/\.css$/, // 匹配到点css结束的文件
                use:[
                    "style-loader", 
                    "css-loader"
                ]
            }
        ]
    }

3.配置less的load

这个Loader叫:less-loader 这个less-loader还需要依赖less模块

npm i less less-loader -D

less需要安装,但不需要配置

js

{
    test:/\.less$/, // 匹配到.less结束的文件
    use:[
           "style-loader",  // 会创建一个style标签,把样式放到style标签中,style标签会放到head标签中
         "css-loader", // 把css文件转成commonjs模块
           "less-loader", // 把less代码转成css代码
   ]
}

4.自动创建html

打包完后的main.js自动塞到index.html中。

webpack@5与html-webpack-plugin模块存在兼容问题(若全局安装了,也需降级)

npm i html-webpack-plugin -D
npm i clean-webpack-plugin -D

js

const HtmlWebpackPlugin = require("html-webpack-plugin")
const { CleanWebpackPlugin } = require("clean-webpack-plugin")

// 配置plugin
    plugins:[
        new HtmlWebpackPlugin({ // 配置自动根据模板创建新的HTML文件,并把打包好的main.js放到的新的Html文件中
            template:"./public/index.html", // 指定模板
        }),
        new CleanWebpackPlugin() // 在打包之前,可以清理dist目录下面没有用的文件
    ]

为了区分每次打包给出口设置标记

filename:"[name].[chunkhash].js",

5.打包图片

需要两个loader: file-loader   url-loader

url-loader:比file-loader更加强大  url-loader依赖于file-loader  配置时,只会配置url-loader

file-loader能处理的url-loader也能处理
npm i file-loader url-loader -D

若想处理html中的图片,需要用到html-load 打包后为base64格式

npm i html-loader -D

js

 // 处理css文件中的背景图片
{
     test:/\.(jpg|png|gif)$/,
        use:[
                   {
                    loader:"url-loader",   // 处理图片,处理字体图标...
                    options:{
                        outputPath: 'images', // 将文件打包到哪里
                        // 如果说图片小于10kb 处理成base64  如果大于10kb 就是一个单独的文件
                        limit:10*1024,  // 10*1024 10kb
                        name:'[hash:24].[ext]'  // 给图上重命名
                     }
                  }
            ]
},
// 处理html文件中的img标签
{
     test:/\.html$/,
     // html-loader 负责处理img 标签引入的图片
     loader:"html-loader"
}

6.打包字体图标

loader叫:file-loader

npm i file-loader -D //之前是安装过的

js

{
   // 处理字体图标
   exclude:/\.(css|js|html|json|less|png|jpg|gif)/,
   loader: "file-loader",
   options: {
        name:'[hash:12].[ext]',
         // 设置输出的文件夹
        outputPath:"source"
   }
}

7.配置开发服务器

需要安装一个模块:webpack-dev-server

npm i webpack-dev-server -D

在pages中配置

"scripts": {
       "serve": "npx webpack-dev-server",    // 在内存中打包的命令
       "build": "npx webpack",     // 在硬盘上打包的命令
       "test": "echo \"Error: no test specified\" && exit 1"
     },

js

devServer:{
        contentBase:path.resolve(__dirname,"dist"),  // 配置开发服务器托管的资源的路由
        port:8080,  // 开发服务器的端口
        open:true,  // 自动打开浏览器
        compress:true // 启动gzip压缩
    }

8.抽离css

需要把打包后不同文件放到的不同的文件夹中.mini-css-extract-plugin

npm i mini-css-extract-plugin -D

js

let MiniCssExtractPlugin = require("mini-css-extract-plugin")
//修改css配置
{
    test:/\.css$/,
    use:[
        // "style-loader",  // 你要抽离css 不能使用style-loader  style-loader是把样式处理成内部样式
        MiniCssExtractPlugin.loader,
        "css-loader"
    ]
},
//plugins配置
new MiniCssExtractPlugin({
    filename: "css/index.css"
})

9.压缩html,css及js代码

压缩HTML:

npm i html-webpack-plugin //之前安装过的

js

new HtmlWebpackPlugin({
            template:"./public/index.html",
            filename:"index.html",
            // 压缩HTML
            minify:{
                removeComments:true,    //移除HTML中的注释
                collapseWhitespace:true    //删除空白符与换行符
            }
}),

压缩css:奇怪:总报错,定义模块时用let就好啦???

安装:npm install --save-dev optimize-css-assets-webpack-plugin

js

let OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin');
new OptimizeCssAssetsPlugin(),

压缩js:

1)如果模式是生产模式 mode是production,默认就会压缩JS代码。

2)使用插件:uglifyjs-webpack-plugin (多打包几次吧!!!)

npm i uglifyjs-webpack-plugin -D

js

new uglifyjs({
    uglifyOptions:{
         output: {
              comments:false
         }
    }
}),

10.处理css兼容性问题

查看css兼容性网站:https://caniuse.com/

postcss-loader 是loader 针对postcss-loader还有很多插件 postcss-loader 处理兼容问题

postcss-preset-env 是postcss-loader的插件 处理不同的浏览器的

npm i postcss-loader  postcss-preset-env -D

js

 {
           test:/\.css$/,
           use:[
               // "style-loader",  // 你要抽离css 不能使用style-loader  style-loader是把样式处理成内部样式
               MiniCssExtractPlugin.loader,
               "css-loader",
               // 配置postcss-loader
               {
                   loader:"postcss-loader",
                   options:{
                           // ident:'postcss',
                           plugins:(loader)=>[
                               require('postcss-preset-env')()
                           ]
                    }
                }
            ]
},

webpack-cli@4版本不兼容,这里使用的@3.3.12版本的

还需要在package.json中配置

"browserslist": {
    "development": [
      "last 10 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ],
    "production": [
      "> 0.2%",
      "not dead"
    ]
}

11.配置ESLint校验js代码

ESLint 是一个插件化并且可配置的 JavaScript 语法规则和代码风格的检查工具。

作用:校验我们写的JS代码是否符合规则(可以自己定制,也可以使用第三方)

如果不满足规则,就报错,打包也打包不成功。

1)安装

npm i eslint eslint-loader eslint-plugin-import eslint-config-airbnb-base -D

2)配置校验js的loader

{
     test:/\.js$/,
     exclude:/node_modules/,
     loader:"eslint-loader",
     // 配置自动修复
     options:{
             fix:true
     }
}

3)page.json中配置

"eslintConfig": {
    "extends": "airbnb-base"
  }

12.处理js的兼容性

我们写的JS代码,在比较低版本的浏览器中并不能识别,需要做兼容性处理。

目的:ES6/7/8/9 ——> 编译老的写法 JS的兼容性处理

npm i babel-loader @babel/core @babel/preset-env -D

babel-loader依赖于 @babel/core @babel/preset-env

按需引入polyfill

npm i @babel/polyfill -D

使用:入口中引入

import "@babel/polyfill"  //非常大缺点:体积非常大    如果说项目非常大,那么你全局引入也OK

如果项目不是那么,全局引入所有的ployfill,就不那么合理了。此时我们就需要按需引入。

如果你要按需引入,需要借助一个模块,叫core-js
安装:npm i core-js    配置这个模块可以达到按需引入的目的。

js

 {
                test:/\.js$/,
                exclude:/node_modules/,
                loader:"babel-loader",
                options:{
                    presets:[
                        [
                            "@babel/preset-env",
                            {
                                // 配置按需引入
                                useBuiltIns:'usage',
                                corejs:{
                                    version:3
                                },
                                // 指定兼容做到哪个版本的浏览器
                                targets:{
                                    chrome:'60',
                                    firefox:'60',
                                    ie:'9',
                                    safari:'10',
                                    edge:'17'
                                }
                            }
                        ]
                    ]
                }
            }

13.配置loader的优先执行

若添加loader比较多,如js用到的loader,该优先执行哪一个,这是个问题

{
      test:/\.js$/,
      exclude:/node_modules/,
      loader:"eslint-loader",
      enforce:"pre",  // 优先执行eslint-loader 通过后再执行其它的loader
      // 配置自动修复
      options:{
            fix:true
      }
},

14.HML热模块替换

js配置

 devServer:{
            contentBase:resolve(__dirname,"dist"),
            port:8080,
            open:true,
            compress:true,
            hot:true, // 支持热模块替换
         }

1)样式模块:支持HMR,必须使用style-loader

  1. HTML模块 由于HTML模块基本上我们不会动 所以基本上不会对HTML文件进行HMR

    如果确实要对HTML文件进行HMR,需要配置多入口:

配置:

entry:["./src/index.js","./public/index.html"],
    // 出口
output:{
    filename:"[name].[chunkhash].js",
    path:path.resolve(__dirname,"dist")
},

3)JS模块 默认是不支持HMR功能

    如果想让它支持HMR功能,需要在**入口JS中配置**, JS模块HMR,不能针对入口文件(针对是非入口)
if (module.hot) {
  // 使用HMR功能
  // 针对vuex.js模块进行HMR
  module.hot.accept('./js/vuex.js', () => {
    logVuex();
  });
}

15.SourceMap调试

在JS模块中,写的JS代码,可能会出错误。由于你的JS模块被打包了,那么你,很难定位到你的错误。

配置js (注:生产模式下打包,和uglifyjs-webpack-plugin有冲突 )

//mode:"production",
devtool:"cheap-module-source-map"

source-map配置选项非常多:
[inline- | hidden- | eval- | nosources- | cheap- | cheap-module ]source-map
可选项目也可以组合搭配。

速度排名:eval > inline > cheap > ....

source-map:  外部映射
    1)生成源代码到构建后代码的映射代码   此映射文件--->帮你精确定位错误
    2)精确  哪个文件   此文件的哪个位置
inline-source-map: 内部映射
    1)并不会生成所谓的映射文件
    2)帮我们定义到哪个文件的哪个位置出错了
hidden-source-map:外部映射
    1)不能帮我们定位到哪个文件的哪个位置出错了
    2)生成所谓的映射文件
eval-source-map: 内部映射
    1)并不会生成所谓的映射文件
    2)帮我们定位到哪个文件的哪个位置出错了
nosources-source-map:
    1)生成所谓的映射文件
    2)能找到哪个文件出错了,但是找不到是此文件的哪个位置出错了

cheap-source-map:外部映射
cheap-module-source-map:外部映射
    1)生成所谓的映射文件
    2)帮我们定位到哪个文件的哪个位置出错了

开发环境:调试友好,构建速度快一点  通常开发时,都是在内存中打包
    eval-source-map : 速度快  定位到错误
    eval-cheap-source-map  速度快  定位到错误  生map文件

生产环境:隐藏源代码(不要定位错误) 代码体积小(不要有map文件)
    hidden-source-map
    nosources-source-map

16.oneof匹配loader

默认情况下,所有的模块进入rules中会都走一遍,没有必要,

可以使用OneOf,只会匹配到一个loader,提升打包速度。

{
                oneof:[
                    // 1,样式相关的  css  less  scss  stylus
                    {
                        test:/\.css$/,
                    },
                    {
                        test:/\.less$/,
                    },
                    {
                        test:/\.js$/,
                    },
                    {  
                        test:/\.(jpg|png|gif)$/,
                    },
                    {
                        test:/\.html$/,
                    },
                    {
                        exclude: /\.(js|json|css|less|jpg|png|gif|html)$/,
                    }
                ]
},

17.设置缓存

1.使用缓存(不足)

1)后端设置 {maxAge:3600*1000}

2)webpack 需要设置 cacheDirectory:true

3)设置打包后的文件名 只有文件名发生改变

output:{
            filename:"js/main.[hash:10].js",
            path:resolve(__dirname,"dist")
},

没有缓存了。 不管代码动没动,每一次打包,都会生成一个新的文件名,文件名变了,重新请求服务器,不会再走缓存

目标:如果代码没有动,走缓存,如果代码动了,不走缓存。

2.重新配置代码获取缓存(修改)

使用contenthash计算代码是否发生改变

output:{
        filename:"js/main.[contenthash:10].js",
        path:resolve(__dirname,"dist")
},
new MiniCssExtractPlugin({
         filename:"css/index.[contenthash:10].css"
}),

18.树摇

参考:https://zhuanlan.zhihu.com/p/41795312

Tree Shaking:
树摇 作用:去除掉无用的代码,减少代码体积。
如:项目中引入elementui 默认是引入了所有的组件,默认webpack支持树摇,项目中没有使用到的组件,webpack不会进行打包。
tree shaking 是一种代码优化技术。

默认开启tree shaking的前提:
1)使用的模块化是ES6模块化
2)基于开发环境
3)webpack版本大于等于4

Tree Shaking用于去掉无用的代码:
有些第三方模块,不想进行Tree Shaking,需要关闭Tree Shaking,如何关闭:

// package.json
"sideEffects": false
//或
"sideEffects": [
    "dist/*",
    "*.css",
    "*.less",
    "xx.js"
  ]

19.代码分割

配置JS多入口:

        如果项目比较大,打包出来的main.js也是非常大,加载就非常多。 引时就可以配置多入口。

1.单入口改为多入口

 entry:{
                index:"./src/index.js",
                router:"./src/js/router.js",
                vuex:"./src/js/vuex.js"
 },

2.分割模块

//webpack配置
optimization:{
        splitChunks:{
            chunks:'all'
        }
}

20.懒加载和预加载

入口配置

懒加载js,用到时加载

// 懒加载
document.getElementById("box1").onclick = function () {
    import(/* webpackChunkName:'router' */"./js/router").then(res=>{
        console.log(res.default())
    })
}

预加载js,提前加载好

// 预加载  webpackPrefetch:true
document.getElementById("box2").onclick = function () {
    import(/* webpackChunkName:'vuex', webpackPrefetch:true */"./js/vuex").then(res=>{
        console.log(res.default())
    })
}

21.PWA

Progressive Web Application 渐进式web应用

通过pwa可以让webapp在没有网络的情况下,看能看到一些数据。

第一步:

 安装:npm i workbox-webpack-plugin

第二步:配置

new WorkboxWebpackPlugin.GenerateSW({
            clientsClaim: true, // 快速启动serverWorker
            skipWaiting: true
})

第三步:在入口文件中注册

 if ('serviceWorker' in navigator) {
            window.addEventListener('load', () => {
                navigator.serviceWorker.register('/service-worker.js').then(registration => {
                console.log('SW registered: ', registration);
                }).catch(registrationError => {
                console.log('SW registration failed: ', registrationError);
                });
            });
        }

第四步:安装一个叫serve的模块 这个模块就可以快速的搭建一台服务器

   npm i serve -g    //如果全局安装了,就可以使用serve命令

第五步:运行 serve -s dist

21.多进程打包

npm i serve -g    如果全局安装了,就可以使用serve命令

js

 {
            loader:"thread-loader",
            options: {
                workers:3  // 开启两个进行进行打包
            }
},

配置排除打包

externals:{
       jquery:"jQuery"
       ....
}

22.配置代理服务器

devServer: {
        // 1,最基础的配置
        contentBase: resolve(__dirname, "dist"),
        port: 8080,
        open: true,
        compress: true,
        hot: true, // 支持热模块替换

        // 2,常用配置
        watchContentBase:true, // 监听contentBase下面所有的文件,就重新的reload
        watchOptions:{
            // 忽略监听的模块
            ignored:/node_modules/
        },

        // 3,跨域开发环境的跨域
        proxy:{   //     axios:  /api/news  访问是开发访问   就会转发到另一个服务器
            //  /news  获取新闻列表    baseURL:http://www.baidu.com
            //  http://www.baidu.com/api/news
            //  http://www.baidu.com/news
            "/api":{
                target:"http://www.baidu.com",  // 后台接口地址
                pathRewrite:{
                    "^/api":""
                }
            }
        }

        // 在vue脚本架中  新建一个vue.config.js
        // 在vue脚手架中,隐藏了所有的webpack配置,如果你想,你需要在vue.config.js中配置
        // 你在vue.config.js中配置完毕后,脚手架会帮你合并到webpack中
    },
    // devtool:"source-map",
    optimization:{
        splitChunks:{
            chunks:'all'
        }
    },
    // 配置排除打包
    externals:{
        jquery:"jQuery"
    },
    resolve:{
        // 配置路由的别名  @  src     $myJs   src/js
        // 优点:路径简写了  缺点:没有提示  鸡肋   @xx/a/b/c
        alias:{
            $myJs:resolve(__dirname,"../../src/js")
        },

        // 配置模块匹配的后缀名  Home
        // 鸡肋
        extensions:[".js",".json",".css",".less"],

        // 配置找node_module的目录
        // modules:[resolve(__dirname,"../../node_modules")]
    }