Re:用webpack从零开始的vue-cli搭建'生活'

有了vue-cli的帮助,我们创建vue的项目非常的方便,使用`vue create`然后选择些需要的配置项就能自动帮我们创建配置好的webpack项目脚手架了,实在是‘居家旅行’必备良药。这次借着学习webpack的机会,不用`vue-cli`搭建一个vue项目。

有了vue-cli的帮助,我们创建vue的项目非常的方便,使用vue create然后选择些需要的配置项就能自动帮我们创建配置好的webpack项目脚手架了,实在是‘居家旅行’必备良药。这次借着学习webpack的机会,不用vue-cli搭建一个vue项目。

注:基于webpack5,其运行于 Node.js v10.13.0+ 的版本。

完整代码:https://github.com/mashiro-cat/learn_webpack

webpack基础

webpack官网:https://webpack.js.org/

webpack中文官网:https://webpack.docschina.org/

安装:

npm i webpack webpack-cli -D

运行:

npx webpack ./src/main.js --mode=development
# 根目录有配置文件
npx webpack

打开文档就能看到五个核心配置点:

  1. 入口(entry)
  2. 输出(output)
  3. loader
  4. 插件(plugin)
  5. 模式(mode)

webpack本身只提供了对js中ES Module和压缩的支持,很多功能都要通过使用loader或者plugin拓展。

webpack.config.js配置文件编写:

module.exports = {
  // 入口 多入口则配置成对象形式
  entry:"",
  // 输出 需使用绝对路径
  output:{},
  // loader
  module:{
    rules:[]
  },
  // 插件
  plugins:[],
  // development 或者 production
  // 生产模式默认开启js和html压缩
  mode:"development"
}

样式资源处理

配置资源输出的路径和名称

输出:

output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'static/js/main.js' // 将js输出到 static/js 目录中
  }

module中:

generator: {
          // 将图片文件输出到 static/imgs 目录中
          // 将图片文件命名 [hash:8][ext][query]
          // [hash:8]: hash值取8位 直接[hash]则不截取
          // [ext]: 使用之前的文件扩展名
          // [name]: 会使用之前的名字
          // [query]: 添加之前的query参数
          filename: "static/imgs/[hash:8][ext][query]",
        },

css处理

安装两个loader,其使用顺序是css-loader会处理css,而将编译的css经style-loader后会动态创建style标签。

css-loader

# 安装
npm i css-loader style-loader -D
rules: [
      // 两个loader顺序按此 它会先使用后面的
      { test: /\.css$/i, use: ["style-loader", "css-loader"] }
    ]
提取css到单独文件

现在是css全部是打包到js中,然后动态插入的。若需要提取到单独文件,则可以借助插件。

// 安装插件
npm i mini-css-extract-plugin -D

// 配置插件
const MiniCssExtractPlugin = require("mini-css-extract-plugin");

// 将styel-loader改为MiniCssExtractPlugin.loader
{
  // 用来匹配 .css 结尾的文件
  test: /\.css$/,
  // use 数组里面 Loader 执行顺序是从右到左
  use: [MiniCssExtractPlugin.loader, "css-loader"],
},

plugins:[
new MiniCssExtractPlugin({
      // 定义输出文件名和目录
      filename: "static/css/main.css",
    }),
]
css兼容处理
// 安装
npm i postcss-loader postcss postcss-preset-env -D

// 配置

{
        // 用来匹配 .css 结尾的文件
        test: /\.css$/,
        // use 数组里面 Loader 执行顺序是从右到左
        use: [
          MiniCssExtractPlugin.loader,
          "css-loader",
          { // 在css-loader之后,预处理器loader之前
            loader: "postcss-loader",
            options: {
              postcssOptions: {
                plugins: [
                  "postcss-preset-env", // 能解决大多数样式兼容性问题
                ],
              },
            },
          },
        ],
      },

控制兼容性:

package.json 文件中添加 browserslist 来控制样式的兼容性的程度:

{
  // 其他省略
  //"browserslist": ["ie >= 8"]
  // 实际开发中我们一般不考虑旧版本浏览器了,所以我们可以这样设置:
  // 所有浏览器的最新两个版本 支持市面上99%浏览器 还没死的浏览器
  "browserslist": ["last 2 version", "> 1%", "not dead"]
}
css压缩

安装插件:

npm i css-minimizer-webpack-plugin -D

webpack配置:

const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");

plugins:[
  // css压缩
  new CssMinimizerPlugin(),
]

预处理器

使用less,scss等预处理都要安装对应的loader进行编译,webpack才能识别处理。

less的使用:

// 安装less-loader
npm i less-loader -D

// 配置
// less-loader将less转为css后还是要交给css-loader处理的
{
  test: /\.less$/,
  use: ["style-loader", "css-loader", "less-loader"]
}

scss, sass的使用:

// 安装
npm i sass-loader sass -D

// 配置
{
  test: /\.s[ac]ss$/,
  use: ["style-loader", "css-loader", "sass-loader"],
},
点击查看完整配置

const path = require('path')

module.exports = {
  // 入口 多入口则配置成对象形式
  entry: "./src/main.js",
  // 输出 需使用绝对路径
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'main.js'
  },
  // loader
  module: {
    rules: [
      // 两个loader顺序按此 它会先使用后面的
      { test: /\.css$/i, use: ["style-loader", "css-loader"] },
      // less-loader将less转为css后还是要交给css-loader处理的
      { test: /\.less$/, use: ["style-loader", "css-loader", "less-loader"] },
      {
        test: /\.s[ac]ss$/,
        use: ["style-loader", "css-loader", "sass-loader"],
      },
    ]
  },
  // 插件
  plugins: [],
  // development 或者 production
  mode: "development"
}

图片资源处理

Webpack4使用file-loader 和 url-loader处理图片资源,而webpack5将那俩都内置了,直接配置开启就可。

{
  test: /\.(png|jpe?g|gif|webp)$/,
  type: "asset",
},

将小于某个大小的图片转化成Base64可添加此配置:

{
        test: /\.(png|jpe?g|gif|webp)$/,
        type: "asset",
        parser: {
          dataUrlCondition: {
            maxSize: 10 * 1024 // 小于10kb的图片会被base64处理
          }
        }
},
点击查看完整配置

const path = require('path')

module.exports = {
  // 入口 多入口则配置成对象形式
  entry: "./src/main.js",
  // 输出 需使用绝对路径
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'main.js'
  },
  // loader
  module: {
    rules: [
      // 两个loader顺序按此 它会先使用后面的
      { test: /\.css$/i, use: ["style-loader", "css-loader"] },
      // less-loader将less转为css后还是要交给css-loader处理的
      { test: /\.less$/, use: ["style-loader", "css-loader", "less-loader"] },
      {
        test: /\.s[ac]ss$/,
        use: ["style-loader", "css-loader", "sass-loader"],
      },
      {
        test: /\.(png|jpe?g|gif|webp)$/,
        type: "asset",
        parser: {
          dataUrlCondition: {
            maxSize: 10 * 1024 // 小于10kb的图片会被base64处理
          }
        }
      },
    ]
  },
  // 插件
  plugins: [],
  // development 或者 production
  mode: "development"
}

其它资源处理

若项目中引用了字体,视频等资源,则是希望不要处理它,直接输出就好了。配置为type: "asset/resource"它就会原封不动的输出了。

{
        // 处理字体图标或者视频等其它资源
        test: /\.(ttf|woff2?|map4|map3)$/,
        type: "asset/resource",
        generator: {
          filename: "static/media/[hash:8][ext][query]",
        },
      }
点击查看完整配置

const path = require('path')

module.exports = {
  // 入口 多入口则配置成对象形式
  entry: "./src/main.js",
  // 输出 需使用绝对路径
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'static/js/main.js', // 将js输出到 static/js 目录中
    clean: true
  },
  // loader
  module: {
    rules: [
      // 两个loader顺序按此 它会先使用后面的
      { test: /\.css$/i, use: ["style-loader", "css-loader"] },
      // less-loader将less转为css后还是要交给css-loader处理的
      { test: /\.less$/, use: ["style-loader", "css-loader", "less-loader"] },
      {
        test: /\.s[ac]ss$/,
        use: ["style-loader", "css-loader", "sass-loader"],
      },
      {
        test: /\.(png|jpe?g|gif|webp)$/,
        type: "asset",
        parser: {
          dataUrlCondition: {
            maxSize: 10 * 1024 // 小于10kb的图片会被base64处理
          }
        },
        generator: {
          // 将图片文件输出到 static/imgs 目录中
          // 将图片文件命名 [hash:8][ext][query]
          // [hash:6]: hash值取6位
          // [ext]: 使用之前的文件扩展名
          // [query]: 添加之前的query参数
          filename: "static/imgs/[hash:6][ext][query]",
        },
      },
      {
        // 处理字体图标或者视频等其它资源
        test: /\.(ttf|woff2?|map4|map3)$/,
        type: "asset/resource",
        generator: {
          filename: "static/media/[hash:8][ext][query]",
        },
      },
    ]
  },
  // 插件
  plugins: [],
  // development 或者 production
  mode: "development"
}

js资源处理

代码质量检测 Eslint

安装:

npm i eslint-webpack-plugin eslint -D

在webpack配置中使用eslint插件

const ESLintWebpackPlugin = require("eslint-webpack-plugin");

plugins: [
    new ESLintWebpackPlugin({
      // 指定检查文件的根目录
      context: path.resolve(__dirname, "src"),
    }),
  ],

编写配置:

配置文件由很多种写法:.eslintrc.*:新建文件,位于项目根目录

  • .eslintrc
  • .eslintrc.js
  • .eslintrc.json

区别在于配置格式不一样package.json 中 eslintConfig:不需要创建文件,在原有文件基础上写,ESLint 会查找和自动读取它们,所以以上配置文件只需要存在一个即可

根目录创建.eslintrc.js配置文件

// .eslintrc.js
module.exports = {
  // 解析配置项
  parserOptions: {
    ecmaVersion: 6, // ES 语法版本
    sourceType: "module", // ES 模块化
  },
  env: {
    node: true, // 启用node中全局变量
    browser: true, // 启用浏览器中全局变量 不开启则像 console Math 等全局变量无法使用
  },
  // 继承规则
  extends: ['eslint:recommended'],
  // 检测规则
  // 自定义的规则会覆盖继承的规则
  // "off" 或 0 - 关闭规则
  // "warn" 或 1 - 开启规则,使用警告级别的错误:warn (不会导致程序退出)
  // "error" 或 2 - 开启规则,使用错误级别的错误:error (当被触发的时候,程序会退出)
  rules: {
    semi: "off", // 禁止使用分号
    'array-callback-return': 'warn', // 强制数组方法的回调函数中有 return 语句,否则警告
    'default-case': [
      'warn', // 要求 switch 语句中有 default 分支,否则警告
      { commentPattern: '^no default$' } // 允许在最后注释 no default, 就不会有警告了
    ],
    eqeqeq: [
      'warn', // 强制使用 === 和 !==,否则警告
      'smart' // https://eslint.bootcss.com/docs/rules/eqeqeq#smart 除了少数情况下不会有警告
    ],
  },
}

babel兼容处理

安装:

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

babel配置编写:

配置文件由很多种写法:

  1. babel.config.*:新建文件,位于项目根目录
  • babel.config.js
  • babel.config.json
  1. .babelrc.*:新建文件,位于项目根目录
  • .babelrc
  • .babelrc.js
  • .babelrc.json

package.json 中 babel:不需要创建文件,在原有文件基础上写

presets 预设:

简单理解:就是一组 Babel 插件, 扩展 Babel 功能

@babel/preset-env: 一个智能预设,允许您使用最新的 JavaScript。

@babel/preset-react:一个用来编译 React jsx 语法的预设

@babel/preset-typescript:一个用来编译 TypeScript 语法的预设

// 创建.babelrc.js
module.exports = {
  presets: ["@babel/preset-env"],
};

webpack增加babel

{
  test: /\.js$/,
  exclude: /node_modules/, // 排除node_modules代码不编译
  loader: "babel-loader",
},

html自动导入处理

安装html-webpack-plugin

npm i html-webpack-plugin -D

webpack配置, 配置好后就会自动的引入所需的js了。

const HtmlWebpackPlugin = require("html-webpack-plugin");

plugins: [
    new HtmlWebpackPlugin({
      // 以 public/index.html 为模板创建文件
      // 新的html文件有两个特点:1. 内容和源文件一致 2. 自动引入打包生成的js等资源
      template: path.resolve(__dirname, "public/index.html"),
    }),
]

webpackSever

使用webpacksever后,在开发时就能自动检测文件变化,并实时编译展示出来了。

// 安装
npm i webpack-dev-server -D

// 配置
devServer: {
    host: "localhost", // 启动服务器域名
    port: "3000", // 启动服务器端口号
    open: true, // 是否自动打开浏览器
  },

运行:

// 此时不会打包生成文件,都是在内存中进行编译的
npx webpack serve
点击查看完整配置

const path = require('path')
const ESLintWebpackPlugin = require("eslint-webpack-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");

module.exports = {
  // 入口 多入口则配置成对象形式
  entry: "./src/main.js",
  // 输出 需使用绝对路径
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'static/js/main.js', // 将js输出到 static/js 目录中
    clean: true
  },
  // loader
  module: {
    rules: [
      // 两个loader顺序按此 它会先使用后面的
      { test: /\.css$/i, use: ["style-loader", "css-loader"] },
      // less-loader将less转为css后还是要交给css-loader处理的
      { test: /\.less$/, use: ["style-loader", "css-loader", "less-loader"] },
      {
        test: /\.s[ac]ss$/,
        use: ["style-loader", "css-loader", "sass-loader"],
      },
      {
        test: /\.(png|jpe?g|gif|webp)$/,
        type: "asset",
        parser: {
          dataUrlCondition: {
            maxSize: 10 * 1024 // 小于10kb的图片会被base64处理
          }
        },
        generator: {
          // 将图片文件输出到 static/imgs 目录中
          // 将图片文件命名 [hash:8][ext][query]
          // [hash:6]: hash值取6位
          // [ext]: 使用之前的文件扩展名
          // [query]: 添加之前的query参数
          filename: "static/imgs/[hash:6][ext][query]",
        },
      },
      {
        // 处理字体图标或者视频等其它资源
        test: /\.(ttf|woff2?|map4|map3)$/,
        type: "asset/resource",
        generator: {
          filename: "static/media/[hash:8][ext][query]",
        },
      },
      { // babel配置
        test: /\.js$/,
        exclude: /node_modules/, // 排除node_modules代码不编译
        loader: "babel-loader",
      },
    ]
  },
  // 插件
  plugins: [
    new ESLintWebpackPlugin({
      // 指定检查文件的根目录
      context: path.resolve(__dirname, "src"),
    }),
    new HtmlWebpackPlugin({ // html处理的插件
      // 以 public/index.html 为模板创建文件
      // 新的html文件有两个特点:1. 内容和源文件一致 2. 自动引入打包生成的js等资源
      template: path.resolve(__dirname, "public/index.html")
    })
  ],
  devServer: {
    host: "localhost", // 启动服务器域名
    port: "666", // 启动服务器端口号
    open: true, // 是否自动打开浏览器
  },
  // development 或者 production
  mode: "development"
}

webpack进阶

使用sourcemap

SourceMap(源代码映射)是一个用来生成源代码与构建后代码一一映射的文件的方案。

通过查看Webpack DevTool文档可知,SourceMap 的值有很多种情况.

开发时我们只需要关注两种情况即可:

开发模式:cheap-module-source-map,优点:打包编译速度快,只包含行映射,缺点:没有列映射

生产模式:source-map,优点:包含行/列映射,缺点:打包编译速度更慢。

配置:

devtool: "cheap-module-source-map",

提升打包速度

HotModuleReplacement:它(HMR/热模块替换):在程序运行中,替换、添加或删除模块,而无需重新加载整个页面。

module.exports = {
  // 其他省略
  devServer: {
    host: "localhost", // 启动服务器域名
    port: "3000", // 启动服务器端口号
    open: true, // 是否自动打开浏览器
    hot: true, // 开启HMR功能(只能用于开发环境,生产环境不需要了)
  },
};

此时 css 样式经过 style-loader 处理,已经具备 HMR 功能了。 但是 js 可以使用vue-loader, react-hot-loader实现。

OneOf配置(开发和正式都能用):

匹配到一条规则就不继续匹配了

module: {
    rules: [
        {
            oneOf: [
                { test: /\.css$/, use: ["style-loader", "css-loader"] },
                .........
            ]
        }
    ]
}

Include/Exclude

如在配置babel排除node_moudels文件夹

使用缓存Cache

每次打包时 js 文件都要经过 Eslint 检查 和 Babel 编译,速度比较慢。我们可以缓存之前的 Eslint 检查 和 Babel 编译结果,这样第二次打包时速度就会更快了

// babel
{
            test: /\.js$/,
            // exclude: /node_modules/, // 排除node_modules代码不编译
            include: path.resolve(__dirname, "../src"), // 也可以用包含
            loader: "babel-loader",
            options: {
              cacheDirectory: true, // 开启babel编译缓存
              cacheCompression: false, // 缓存文件不要压缩
            },
          },


// Eslint
new ESLintWebpackPlugin({
      // 指定检查文件的根目录
      context: path.resolve(__dirname, "../src"),
      exclude: "node_modules", // 默认值
      cache: true, // 开启缓存
      // 缓存目录
      cacheLocation: path.resolve(
        __dirname,
        "../node_modules/.cache/.eslintcache"
      ),
    })

打包启用多线程:

开启线程也需要时间,小项目可能提升不明显。

npm i thread-loader -D安装loader,然后配置:

// nodejs核心模块,直接使用
const os = require("os");
const TerserPlugin = require("terser-webpack-plugin"); // webpack自带的js压缩模块

// cpu核数
const threads = os.cpus().length;

// babel使用多线程
{
            test: /\.js$/,
            // exclude: /node_modules/, // 排除node_modules代码不编译
            include: path.resolve(__dirname, "../src"), // 也可以用包含
            use: [
              {
                loader: "thread-loader", // 开启多进程
                options: {
                  workers: threads, // 数量
                },
              },
              {
                loader: "babel-loader",
                options: {
                  cacheDirectory: true, // 开启babel编译缓存
                },
              },
            ],
          },

// Eslint使用多线程
new ESLintWebpackPlugin({
      // 指定检查文件的根目录
      context: path.resolve(__dirname, "../src"),
      exclude: "node_modules", // 默认值
      cache: true, // 开启缓存
      // 缓存目录
      cacheLocation: path.resolve(
        __dirname,
        "../node_modules/.cache/.eslintcache"
      ),
      threads, // 开启多进程
    }),

// js压缩使用多线程
optimization: {
    minimize: true,
    minimizer: [
      // css压缩也可以写到optimization.minimizer里面,效果一样的
      new CssMinimizerPlugin(),
      // 当生产模式会默认开启TerserPlugin,但是我们需要进行其他配置,就要重新写了
      new TerserPlugin({
        parallel: threads // 开启多进程
      })
    ],
  },

减少的代码体检

Tree Shaking: 默认开启,通常用于描述移除 JavaScript 中的没有使用上的代码。

Babel优化:

@babel/plugin-transform-runtime: 禁用了 Babel 自动对每个文件的 runtime 注入,而是引入 @babel/plugin-transform-runtime 并且使所有辅助代码从这里引用。

// 安装
npm i @babel/plugin-transform-runtime -D

// 配置
{
                loader: "babel-loader",
                options: {
                  cacheDirectory: true, // 开启babel编译缓存
                  cacheCompression: false, // 缓存文件不要压缩
                  plugins: ["@babel/plugin-transform-runtime"], // 减少代码体积
                },
              },

优化代码运行性能

打包代码分块

Preload / prefetch

使用Core-js

使用PWA

从零开始搭建vue-webpack项目

使用的库:

设置环境变量:

https://www.npmjs.com/package/cross-env

// 安装
npm install --save-dev cross-env

// package.json

vue-loder文档: https://vue-loader.vuejs.org/zh/

安装vue-loader

npm i vue
npm install -D vue-loader vue-template-compiler

module: {
    rules: [
      // ... 其它规则
      {
        test: /\.vue$/,
        loader: 'vue-loader'
      }
    ]
  },
  plugins: [
    // 请确保引入这个插件!
    new VueLoaderPlugin()
  ]

样式处理

style-loader 替换为 vue-style-loader,并安装预处理器loader

npm i -D css-loader vue-style-loader less-loader less-loader sass-loader stylus-loader

设置拓展名自动布局

resolve: {
    extensions: [".vue", ".js", ".json"], // 自动补全文件扩展名,让vue可以使用
  },

Eslint配置指定为vue的

npm i -D @babel/eslint-parser

// .eslintrc.js
module.exports = {
  root: true,
  env: {
    node: true,
  },
  extends: ["plugin:vue/vue3-essential", "eslint:recommended"],
  parserOptions: {
    parser: "@babel/eslint-parser",
  },
};

Bable配置

npm i -D @vue/cli-plugin-babel

// babel.config.js
module.exports = {
  presets: ["@vue/cli-plugin-babel/preset"],
};

提供对js的变量,解决页面警告

// 解决页面警告
new DefinePlugin({
  __VUE_OPTIONS_API__: "true",
  __VUE_PROD_DEVTOOLS__: "false",
}),

除了以上vue中专用的配置,然后加上less,scss的loader,把前面的html插件加上去。就是一个基本的vue-cli了。完整的配置可以看最前面的仓库链接。

优化

按需引入第三库

如 elment plus 按需引入可参照其官网进行配置

https://yk2012.github.io/sgg_webpack5/

https://vue-loader.vuejs.org/zh/

https://webpack.docschina.org/