前端工程化-Webpack 进阶-区分打包环境、自定义 plugin 和 loader

以下皆为拉勾教育课件内笔记

区分打包环境

通过环境变量区分

详情:https://www.webpackjs.com/guides/environment-variables/

命令行中指定参数:
# Webpack 5 
webpack --env production 

# Webpack 4 
webpack --env.production
配置文件中根据环境参数进行判断:
// webpack.config.js 
module.exports = (env, argv) => { 
  const config = { 
    mode: 'development' 
    // 开发配置 
  } 
  
  if (env.production) { 
    config.mode = 'production' 
    // ⽣产配置 
  } 

  return config 
}

通过配置文件区分

在打包命令中指定配置文件:
# 开发环境打包
webpack --config webpack.dev.conf.js 

# ⽣产环境打包 
webpack --config webpack.prod.conf.js

有些配置在开发环境和生产环境都需要,因此,我们需要声明⼀个公共配置文件 webpack.base.conf.js 使用时,我们可以将 base 合并到 dev 或 prod 中。

配置文件合并

此时,我们需要⼀个合并配置的插件:webpack-merge
详情:https://www.npmjs.com/package/webpack-merge

声明通用配置:
const { resolve } = require('path') 

module.exports = { 
  // ⼊⼝⽂件 
  entry: './src/index.js', 

  // 出⼝配置
  output: {
    // 输出⽬录(输出⽬录必须是绝对路径) 
    path: resolve(__dirname, './dist'), 
    // 输出⽂件名称
    filename: 'bundle.js' 
  }, 
  
  // ...... 
}
声明开发配置:
// webpack.dev.conf.js 
const { merge } = require('webpack-merge') 
const baseWebpackConfig = require('./webpack.base.conf') 
const webpack = require('webpack') 

const devWebpackConfig = merge(baseWebpackConfig, { 
  mode: 'development', 

  plugins: [ 
    new webpack.DefinePlugin({ 
      // 开发环境下的接⼝地址 
      // 变量后⾯的值,是⼀段代码⽚段 
      API_BASE_URL: JSON.stringify('http://apidev.example.com') 
    }), 
  ] 

  // ...... 
})
声明生产配置:
// webpack.prod.conf.js 
const { merge } = require('webpack-merge') 
const baseWebpackConfig = require('./webpack.base.conf') 
const webpack = require('webpack') 

const devWebpackConfig = merge(baseWebpackConfig, { 
  mode: 'production', 
  plugins: [ 
    new webpack.DefinePlugin({ 
      // ⽣产环境下的接⼝地址 
      // 变量后⾯的值,是⼀段代码⽚段 
      API_BASE_URL: JSON.stringify('http://apiprod.example.com') 
    }), 
  ]
   
  // ...... 
})

自定义 plugin

简介

Webpack 是基于插件机制的。
Webpack 插件是⼀个具有 apply 方法的 JavaScript 对象。apply 方法会被 webpack compiler 调用, 并且在整个编译生命周期都可以访问 compiler 对象。
插件原理:通过在生命周期的钩子中挂载函数,来实现功能扩展。
插件详情:https://webpack.docschina.org/concepts/plugins/

生命周期

生命周期就是整个生命过程中的关键节点。

  • 人:出生 -> 入学 -> 毕业 -> 结婚 -> 生子 -> 死亡
  • 程序:初始化 -> 挂载 -> 渲染 -> 展示 -> 销毁

钩子

  • 钩子是提前在可能增加功能的地方,埋好(预设)⼀个函数
  • 简单理解:钩子是生命周期中的函数

比如坐火车经过某个站点的时候,我们可能会做些事情。站点就是可能增加功能的地方,可以理解为钩子。

Webpack 的钩⼦详情:https://www.webpackjs.com/api/compiler-hooks/

常用钩子:

钩子 描述 类型
environment 环境准备好 SyncHook
compile 编译开始 SyncHook
compilation 编译结束 SyncHook
emit 打包资源到 output 之前 AsyncSeriesHook
afterEmit 打包资源到 output 之后 AsyncSeriesHook
done 打包完成 SyncHook

钩子之间有明确的先后顺序

自定义插件

声明自定义插件

// 声明⾃定义插件
class MyPlugin {
  constructor(options) { 
    console.log('插件配置选项', options) 
    this.userOptions = options || {} 
  } 
  
  // 必须声明 apply ⽅法 
  apply(compiler) { 
    // 在钩⼦上挂载功能 
    compiler.hooks.emit.tap('MyPlugin', compilation => { 
      // compilation 是此次打包的上下⽂ 
      for (const name in compilation.assets) { 
        console.log(name) 
        // 针对 css ⽂件,执⾏相关操作 
        // if (name.endsWith('.css')) { 
        if (name.endsWith(this.userOptions.target)) { 
          // 获取处理之前的内容 
          const contents = compilation.assets[name].source() 
          // 将原来的内容,通过正则表达式,删除注释 
          const noComments = contents.replace(/\/\*[\s\S]*?\*\//g , '') 
          // 将处理后的结果,替换掉 
          compilation.assets[name] = { 
            source: () => noComments, 
            size: () => noComments.length 
          } 
        } 
      }
    })
  } 
} 
module.exports = MyPlugin

使用自定义插件

// 引⼊⾃定义插件 
const MyPlugin = require('./plugin/MyPlugin') 

module.exports = (env, argv) => { 
  // ...... 

  // 插件配置 
  plugins: [ 
    // 引⼊⾃定义插件 
    new MyPlugin({ 
      target: '.css' 
    }) 
  ] 
  
  // ...... 
}

自定义 loader

Loader 本质上就是⼀个 ESM 模块,它导出⼀个函数,在函数中对打包资源进行转换。

自定义loader

声明⼀个读取 markdown(.md)文件内容的 loader

  • marked(将 markdown 语法转成 html)
  • loader-utils(接受 loader 的配置项)

先安装上述两个包:npm i -D marked loader-utils

const marked = require('marked') 
const { getOptions } = require('loader-utils') 

// 导出函数 (建议使⽤普通函数) 
module.exports = function(source) {
  // 获取 loader 的配置项 
  const options = getOptions(this) 
  console.log('my loader', options) 
  
  // return 'my loader' 
  
  // return 'console.log("my loader")' 

  const html = marked(source) 

  // "<h1 id="关于">关于</h1><p>我是张三</p>" 
  // 直接返回,可能因为引号的问题,导致报错 
  // return `module.exports = "${html}"` 

  // return `module.exports = ${JSON.stringify(html)}` 
  
  // 直接返回 html,交给下⼀个 loader 进⾏处理 
  return html 
}

如果你声明的 loader 是处理管道中的最后⼀环,则返回结果要求必须是 JavaScript 代码。如果中间有多个 loader 只需要保证最后一个 loader 的返回结果是 JavaScript 。

使用自定义 loader

module.exports = (env, argv) => { 
  // ...... 
  
  // 模块配置 
  module: { 
    rules: [ 
      { 
        test: /\.md$/i, 
        // use: './loader/markdown-loader' 
        use: [ 
          'html-loader', 
          // './loader/markdown-loader' 
          { 
            loader: './loader/markdown-loader', 
            options: { 
              size: 20 
            } 
          } 
        ] 
      },
    ] 
  } 

  // ...... 
}
©著作权归作者所有,转载或内容合作请联系作者
禁止转载,如需转载请通过简信或评论联系作者。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,386评论 6 479
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,939评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,851评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,953评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,971评论 5 369
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,784评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,126评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,765评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,148评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,744评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,858评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,479评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,080评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,053评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,278评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,245评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,590评论 2 343

推荐阅读更多精彩内容