webpack配置项以及常用的plugin和loader

webpack原理及基础配置:

Webpack 的核心原理是将项目中的所有文件视为模块,包括 JS 文件、CSS 文件、图片、字体文件等等。Webpack通过一个给定的主文件(如:index.js)开始找到项目的所有依赖文件,使用loaders处理它们,plugin可以压缩代码和图片,把所有依赖打包成一个 或多个bundle.js文件(捆bundle)浏览器可识别的JavaScript文件。

const path = require('path')
// 默认创建一个html文件,并自动将打包后的资源(js/css)引入
const HTMLWebpackPlugin = require('html-webpack-plugin')
// 删除上一次打包的文件
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
// 打包过后的css在js文件里,该插件可以把css单独抽出来,并自动引入index.html中(通过<link href="main.css" rel="stylesheet">引入)
const MiniCssExtractPlugin = require("mini-css-extract-plugin");

// webpack 中的所有的配置信息都写在 module.exports中
module.exports = {
  mode: 'development',
  // 指定入口文件
  entry: "./src/index.ts",
  // 指定打包文件所在的目录
  output: {
    // 指定打包文件的目录
    path: path.resolve(__dirname, 'dist'),
    // 打包后的文件名
    filename: 'bundle.js',
    // 告诉webpack不适用箭头函数:因为webpack内部会使用箭头函数,不能使用babel转换他内部使用的东西
    environment: {
      arrowFunction: false
    }
  },
  // 指定webpack打包时要使用的模块
  module: {
    // 指定要加载的规则
    rules: [
      {
        // test指定的是规则生效的文件
        test: /\.ts$/,
        // 要使用的laoder
        use:[
          // 配置babel
          {
            // 指定加载器
            loader: "babel-loader",
            // 设置babel
            options: {
              // 设置预定义的环境
              presets: [
                [
                  // 指定环境的插件
                  "@babel/preset-env",
                  // 配置信息
                  {
                    // 要兼容的目标浏览器
                    targets: {"chrome": "99", "ie": "11"},
                    // 指定corejs的版本:corejs作用就是
                    "corejs": "3",
                    // 使用corejs的方式:按需引入
                    "useBuiltIns": "usage"
                  }
                ]
              ]
            }
          },
          // 将ts转为js
          'ts-loader'
        ],
        // 要排除的文件
        exclude: /node-modules/
      },
      // style-loader 和 MiniCssExtractPlugin 不同之处在于 style-loader 在页面创建一个style标签,将样式插入该标签内
      // MiniCssExtractPlugin 将打包后的css文件单独抽离出来,以<link href="xxx"></link>的形式引入页面,这个地方也可以用 style-loader 替换
      // {test: /\.css$/i, use: ['style-loader', 'css-loader']}
      {test: /\.css$/i, use: [MiniCssExtractPlugin.loader, 'css-loader']}
    ],
  },
  plugins: [
    new HTMLWebpackPlugin({
      // 根据该模板html生成一个html
      template: "./src/index.html"
    }),
    // 清除上一次打包的文件
    new CleanWebpackPlugin(),
    new MiniCssExtractPlugin()
  ],
  resolve: {
    // ts和js扩展名都将作为模块,但是注意引入模块的时候不要写.ts
    extensions: [".ts", ".js"]
  }
}

注意:以下资源都是用webpack中的none模式打包便于查看打包后的文件

 loaders(加载器)

Webpack允许使用加载器来预处理文件,从而将资源模块转换成js代码。这允许你绑定JavaScript之外的任何静态资源。例如我们可以使用css文件,通过loaders最终被编为js代码。

css-loader

配置:

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

我们先要通过import()在js文件中引入css建立依赖关系

css-loader会将css文件编译成js代码:我们可以再打包后的文件中看到:

___CSS_LOADER_EXPORT___.push([module.id, `p {
  background-color: red;
}`, ""]);

style-loader

打包后我们可以看到bundle.js文件中有个insertStyleElement函数:创建一个style标签

/***/ ((module) => {
/* istanbul ignore next  */
function insertStyleElement(options) {
  var element = document.createElement("style");
  options.setAttributes(element, options.attributes);
  options.insert(element, options.options);
  return element;
}
module.exports = insertStyleElement;

/***/ }),

file-loader

配置:

module: {
    rules: [
      {test: /.jpg$/, use: 'file-loader'}
    ]
  }

加载图片、字体等资源文件用法跟上面的loader一样在配置项中的modules.rules中配置,打包后的部分代码如下:

//默认导出该资源模块
const __WEBPACK_DEFAULT_EXPORT__ = (__webpack_require__.p + "d903073b89d0bb6d395f96fea59059e2.jpg");

// 导入该资源模块
var _tup_jpg__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1);

url-loader

配置:

module: {
    rules: [
      {test: /.jpg$/, loader: 'url-loader', options: {limit: 10 *1024}} // 限制大小10kb
    ]
  }

使用base64编码资源文件,这种loader适合处理小文件,以减少向服务器请求的次数,大文件还是需要file-loader处理,所以url-loader要分file-loader搭配使用。

打包后的代码:就是将文件转换成base64了。

注意:当我们需要打包css文件中的资源文件时例如我们用background-image: url(tup.jpg);方式引入了一个图片,由于webpack5 file-loader和url-loader被弃用,我们需要向下面这样配置:

{
  test: /.jpg$/,
  use: {
    loader: 'url-loader',
    options: { limit: 3 * 1024, esModule: false},
  },
  type: "javascript/auto"
},

 或者我们可以使用官方推荐的 asset module 来加载资源文件,也可以达到上面的效果用法如下:

官网链接:Asset Modules | webpack

 module: {
   rules: [
     {
       test: /\.png/,
       type: 'asset/resource'
     }
   ]
 },

babel-loader

注意:babel-loader还需要配合其他的包来使用:

npm install -D babel-loader @babel/core @babel/preset-env
  • babel-loader:babel解析es6+语法的桥梁
  • @babel/core:babel-core 的作用是把 js 代码分析成ast (抽象语法树) ,方便各个插件分析语法进行相应的处理。有些新语法在低版本 js 中是不存在的,如箭头函数,rest 参数,函数默认值等,这种语言层面的不兼容只能通过将代码转为 ast,分析其语法后再转为低版本 js
  • @babel/preset-env:一组插件的集合,用于转换es6+等最新语法

配置:

module: {
   rules: [
     {
       test: /.js$/,
       use: {
         loader: 'babel-loader',
         options: {
           presets: ['@babel/preset-env']
         }
       }
     }}
   ]
 }

html-loader

前面我们介绍了css中和js中加载资源模块的方式,下面我们介绍html中加载资源模块的方式,当然我们需要在我们的代码中通过import引入html文件,如下:

// footer.html中
<footer>
  <img src="tup.jpg" alt="better" width="156">
  <a href="tup.jpg">dovnasdf</a>
</footer>

// index.js中
import htmlStr from './footer.html'
document.write(htmlStr)

// webpack.config.js 中在module.rules中
{
  test: /.html$/,
  use: {
    loader: 'html-loader',
    options: {
      sources: {
        list: [
          {
            tag: 'img',
            attribute: 'src',
            type: 'src',
          },
          {
            tag: 'a',
            attribute: 'href',
            type: 'src'
          }
        ]
      }
    }
  }
}

根据官网我们可以不设置sources为true(默认值),默认情况下,每个可加载属性都将被导入,但是a标签的href不会被导入。所以我们单独设置。

官网链接:https://webpack.js.org/loaders/html-loader/#root

开发一个简易的loader

Loader 本质上是导出为函数的 JavaScript 模块。它接收资源文件或者上一个 Loader 产生的结果作为入参,也可以用多个 Loader 函数组成 loader chain(链),最终输出转换后的结果。

我们可以自己开发一个markdown-loader用来处理.md文件将其转化为html

// markdown-loader.js中
const marked = require('marked')
module.exports = function (source) {
  // source是匹配到的资源
  let res = marked.parse(source)
  // 返回的结果会交给html-loader处理
  return res
};

// webpack.config.js中module.rules中的配置
{
  test: /.md$/,
  // 此处我们写的是loader的相对路径也可以
  use:['html-loader', './src/markdown-loader.js']
},

plugins

Plugin 是用来扩展 Webpack 功能的,通过在构建流程里注入钩子实现,它给 Webpack 带来了很大的灵活性。  Webpack 基于观察者模式,在运行的生命周期中会广播出许多事件,Plugin 通过监听这些事件,就可以在特定的阶段执行自己的插件任务,Plugin 可以监听这些事件,在合适的时机通过 Webpack 提供的 API 改变输出结果。

上面代码中我们介绍了一些常用的plugins,我们这里就不在介绍了。

实现一个plugin

我们实现一个去掉打包后的js文件的中的注释

class MyPlugin {
  apply(compiler) {
    compiler.hooks.emit.tap('MyPlugin', compilation => {
      // complilation => 可以理解为此次打包的上下文
      for (const name in compilation.assets) {
        // name 就是每次打包后的文件名称
        if (name.endsWith('.js')) {
          // 通过source拿到文件的内容
          const contents = compilation.assets[name].source()
          // 替换注释
          const withoutComments = contents.replace(/\/\*\*+\*\//g, '')
          compilation.assets[name] = {
            source: () => withoutComments, // 返回修改后的内容
            size: () => withoutComments.length // 返回内容的大小
          }
        }
      }
    })
  }
}

Source Map

 Source Map 就是一个信息文件,里面存储了代码打包转换后的位置信息,实质是一个 json 描述文件,维护了打包前后的代码映射关系。在开发模式下默认是启用了Source Map的,但是代码出错后定位到的位置不是源代码的位置,而是打包后的文件位置,所以我们需要手动更改Source Map的模式

配置:

// 定位到行数和列数的同时,展示具体报错的源码
devtool: 'source-map'
// devtool: 'eval-source-map'

当我们配置后在打包目录中会出现一个.map文件,这个文件就是Source Map文件。打包后的bundle.js文件后面会有一个://# sourceMappingURL=bundle.js.map 注释,就是该注释标记了Source Map的位置,浏览器根据这个才能找到源代码的位置。

配置:

devtool: 'nosources-source-map' // 只暴露出错的行号,不展示源码

development模式下建议使用source-map,production环境下不建议开启,或者使用nosources-source-map。当然不仅仅只有这几种模式

不同环境中的webpack的配置

使用merge函数

首先要安装 webpack-merge 这个包,用于然后分别创建

webpack.common.js:用于设置公共的配置

webpack.dev.js:用于设置开发环境下的配置

webpack.prod.js:用于设置生产环境下的配置

// webpack.common.js
const path = require('path')
const common = require('./webpack.common')
const {merge} = require('webpack-merge')
const {CleanWebpackPlugin} = require('clean-webpack-plugin')

module.exports = merge(common, {
  mode: 'development',
  output: {
    path: path.resolve(__dirname, 'dist1'),
    filename: 'bundle1.js'
  },
  plugins: [
    new CleanWebpackPlugin()
  ]
})


// webpack.prod.js
// 导入公共的webpack配置
const common = require('./webpack.common')
const {merge} = require('webpack-merge')
const {CleanWebpackPlugin} = require('clean-webpack-plugin')
const CopyWebpackPlugin = require('copy-webpack-plugin')

// 使用merge函数合并公共和生产环境的配置
module.exports = merge(common, {
  mode: 'production',
  plugins: [
    new CleanWebpackPlugin(),
    new CopyWebpackPlugin({patterns: [
      { from: "public", to: "" },
    ]})
  ]
})


// webpack.dev.js
const common = require('./webpack.common')
const {merge} = require('webpack-merge')

module.exports = merge(common, {
  mode: 'development',
})

module.exports导出函数的形式

const path = require('path')
const HTMLWebpackPlugin = require('html-webpack-plugin')
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const CopyWebpackPlugin = require('copy-webpack-plugin')

module.exports = (env, argv) => {
// config为development模式下的配置
  const config = {
    mode: 'development',
    entry: "./src/index.js",
    output: {
      path: path.resolve(__dirname, 'dist'),
      filename: 'bundle1.js',
      environment: {
        arrowFunction: false
      }
    },
    module: {
      rules: [
        {test: /\.css$/i, use: [MiniCssExtractPlugin.loader, 'css-loader']}
      ],
    },
    plugins: [
      new HTMLWebpackPlugin({
        template: "./src/index.html"
      }),
      new MiniCssExtractPlugin()
    ]
  }
  if(env.production) {
    // production环境下
    config.mode = 'production'
    config.devtool = 'nosources-source-map'
    config.plugins = [
      ...config.plugins,
      new CleanWebpackPlugin(),
      new CopyWebpackPlugin({patterns: [
        { from: "public", to: "" },
      ]})
    ]
  }
  // 返回该配置
  return config
}

DefinePlugin

DefinePlugin 允许在 编译时 将你代码中的变量替换为其他值或表达式。这在需要根据开发模式与生产模式进行不同的操作时,非常有用。例如,在不同环境下我们需要设置不同的api地址。

使用:

plugins: [
    new webpack.DefinePlugin({
        'process.env.BASE_URL': JSON.stringify('https://www.xx.com')
    })
]

代码分离

代码分离是 webpack 中最引人注目的特性之一。此特性能够把代码分离到不同的 bundle 中,然后便能按需加载或并行加载这些文件。代码分离可以用于获取更小的 bundle、控制资源加载优先级,如果使用合理,会极大减小加载时间。

目录结构(包括打包后的结果)

|- package.json
|- yarn.lock
|- webpack.config.js
|- /dist
  |- useCommon1-bundle.js
  |- useCommon1.html
  |- useCommon2-bundle.js
  |- useCommon2.html
|- /src
  |- common.js
  |- useCommon1.js
  |- useCommon1.html
  |- useCommon1.css
  |- useCommon2.js
  |- useCommon2.html
  |- useCommon2.css

 其中我们会useCommon1.js和useCommon2.js中引入common.js模块并分别引入这两个模块对应的css,dist目录里面还会有两个css文件我们暂时先不说明,最后会细说

wepack.config.js的配置:

const path = require('path')
const HTMLWebpackPlugin = require('html-webpack-plugin')
module.exports = {
  mode: 'development',
  entry: {
    // 配置多入口
    useCommon1: './src/useCommon1.js',
    useCommon2: './src/useCommon2.js'
  },
  output: {
    path: path.resolve(__dirname, 'dist'),
    // 输出的文件名,name为entry配置的key
    filename: '[name]-bundle.js'
  },
  module: {
    rules: [
      {
        test: /.css$/,
        use: ['style-loader', 'css-loader']
      }
    ]
  },
  plugins: [
    new HTMLWebpackPlugin({
      template: './src/useCommon1.html',
      filename: 'useCommon1.html'
    }),
    new HTMLWebpackPlugin({
      template: './src/useCommon2.html',
      filename: 'useCommon2.html'
    })
  ]
}

观察打包后的useCommon1-bundle.js和useCommon2-bundle.js中我们发现都有共同的模块common.js中的内容,所以这种方式存在弊端:

  • 如果入口 chunk 之间包含一些重复的模块,那么这些重复模块会被引入到各个 bundle 中。
  • 这种方法不够灵活,并且不能动态地拆分应用程序逻辑中的核心代码。

所以我们可以将公共的模块提取出来,配置如下:

const path = require('path')
const HTMLWebpackPlugin = require('html-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
module.exports = {
  mode: 'development',
  entry: {
    // 配置多入口
    useCommon1: {
      import: './src/useCommon1.js',
      dependOn: 'shared'
    },
    useCommon2: {
      import: './src/useCommon2.js',
      dependOn: 'shared'
    },
    shared: './src/common.js'
  },
  output: {
    path: path.resolve(__dirname, 'dist'),
    // 输出的文件名,name为entry配置的key
    filename: '[name]-bundle.js'
  },
  module: {
    rules: [
      {
        test: /\.css$/,
        use: ['style-loader', 'css-loader']
      }
    ]
  },
  plugins: [
    new HTMLWebpackPlugin({
      template: './src/useCommon1.html',
      filename: 'useCommon1.html',
      // 因为该插件会注入所有打包结果的js文件,所以我们设置一个chunks单独引入某一个打包结果
      chunks: ['useCommon1']
    }),
    new HTMLWebpackPlugin({
      template: './src/useCommon2.html',
      filename: 'useCommon2.html',
      chunks: ['useCommon2']
    })
  ]
}

此时我们打包后会出现一个单独的公共模块:shared-bundle.js

关于上面的css样式问题,因为我们需要在两个不同的.html文件中引入不同的css,所以我们不能使用style-loader(踩了很长时间的坑),因为我们使用了多个入口点,会导致样式文件在页面中的加载和注入方式发生变化。所以我建议使用使用 MiniCssExtractPlugin 插件具体配置如下:

const path = require('path')
const HTMLWebpackPlugin = require('html-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
module.exports = {
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [MiniCssExtractPlugin.loader, 'css-loader']
      }
    ]
  },
  plugins: [
    new MiniCssExtractPlugin()
  ]
}
  • 10
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值