webpack多页应用架构系列(七):开发环境、生产环境傻傻分不清楚?

本文首发于Array_Huang的技术博客——实用至上,非经作者同意,请勿转载。
原文地址:https://segmentfault.com/a/1190000006952432
如果您对本系列文章感兴趣,欢迎关注订阅这里:https://segmentfault.com/blog/array_huang

前言

开发环境与生产环境分离的原因如下:

  • 在开发时,不可避免会产生大量debug又或是测试的代码,这些代码不应出现在生产环境中(也即不应提供给用户)。
  • 在把页面部署到服务器时,为了追求极致的技术指标,我们会对代码进行各种各样的优化,比如说混淆、压缩,这些手段往往会彻底破坏代码本身的可读性,不利于我们进行debug等工作。
  • 数据源的差异化,比如说在本地开发时,读取的往往是本地mock出来的数据,而正式上线后读取的自然是API提供的数据了。

如果硬是要在开发环境和生产环境用完全一样的代码,那么必然会付出沉重的代价,这点想必也不用多说了。

下面主要针对两点来介绍如何分离开发环境和生产环境:一是如何以不同的方式进行编译,也即如何分别形成开发环境及生产环境的webpack配置文件;二是在业务代码中如何根据环境的不同而做出不同的处理。

如何分离开发环境和生产环境的webpack配置文件

如果同时把一份完整的开发环境配置文件和一份完整的生产环境配置文件列在一起进行比较,那么会出现以下三种情况:

  • 开发环境有的配置,生产环境不一定有,比如说开发时需要生成sourcemap来帮助debug,又或是热更新时使用到的HotModuleReplacementPlugin
  • 生产环境有的配置,开发环境不一定有,比如说用来混淆压缩js用的UglifyJsPlugin
  • 开发环境和生产环境都拥有的配置,但在细节上有所不同,比如说output.publicPath,又比如说css-loader中的minimizeautoprefixer参数。

更重要的是,实际上开发环境和生产环境的配置文件的绝大部分都是一致的,对于这一致的部分来说,我们坚决要消除冗余,否则后续维护起来不仅麻烦,而且还容易出错。

怎么做呢?

答案很简单:分拆webpack配置文件成N个小module。原先我们是一个完整的配置文件,有好几百行,从头看到尾都头大了,更别说分离不分离的了。下面来看看我分离的结果:

├─webpack.dev.config.js # 开发环境的webpack配置文件(无实质内容,仅为组织整理)
├─webpack.config.js # 生产环境的webpack配置文件(无实质内容,仅为组织整理)
├─webpack-config # 存放分拆后的webpack配置文件
  ├─entry.config.js # webpack配置中的各个大项,这一级目录里的文件都是
  ├─module.config.js
  ├─output.config.js
  ├─plugins.dev.config.js # 俩环境配置中不一致的部分,此文件由开发环境配置文件webpack.dev.config.js来加载
  ├─plugins.product.config.js # 俩环境配置中不一致的部分,此文件由生产环境配置文件webpack.config.js来加载
  ├─resolve.config.js
  │  
  ├─base # 主要是存放一些变量
  │   ├─dir-vars.config.js
  │   ├─page-entries.config.js
  │      
  ├─inherit # 存放生产环境和开发环境相同的部分,以供继承
  │   ├─plugins.config.js
  │      
  └─vendor # 存放webpack兼容第三方库所需的配置文件
      ├─eslint.config.js
      ├─postcss.config.js

文件目录结构看过了,接下来看一下我是如何组织整理最后的配置文件的:

/* 开发环境webpack配置文件webpack.dev.config.js */
module.exports = {
  entry: require('./webpack-config/entry.config.js'),

  output: require('./webpack-config/output.config.js'),

  module: require('./webpack-config/module.config.js'),

  resolve: require('./webpack-config/resolve.config.js'),

  plugins: require('./webpack-config/plugins.dev.config.js'),

  eslint: require('./webpack-config/vendor/eslint.config.js'),

  postcss: require('./webpack-config/vendor/postcss.config.js'),
};

这样,你就可以很轻松地处理开发/生产环境配置文件中相同与不同的部分了。

如何分别调用开发/生产环境的配置文件呢?

还记得我在《webpack多页应用架构系列(二):webpack配置常用部分有哪些?》里讲过,我们在控制台调用webpack命令来启动打包时,可以添加上--config参数来指定webpack配置文件的路径吗?我们可以配合上npm scripts来使用,在package.json里定义:

  "scripts": {
    "build": "node build-script.js && webpack --progress --colors",
    "dev": "node build-script.js && webpack --progress --colors --config ./webpack.dev.config.js",
    "watch": "webpack --progress --colors --watch --config ./webpack.dev.config.js"
  },

这样一来,当我们开发的时候就可以使用npm run devnpm run watch,而到要上线打包的时候就运行npm run build

业务代码如何判断生产/开发环境

在业务代码里要判断生产/开发环境其实很简单,只需一个变量即可:

if (IS_PRODUCTION) {
    // 做生产环境该做的事情
} else {
    // 做开发环境该做的事情
}

这么一来,关键就在于这变量IS_PRODUCTION是怎么来的了。

在我还没分离开发和生产环境时,我用的办法是,开发时在业务代码所使用的配置文件中把这变量设为false,而在最后打包上线时就手动改为true。这种方法我用过一段时间,非常繁琐,而且经常上线后发现,我嘞个去怎么ajax读的是我本地的mock服务器。

怎么做呢?

我参考了许多文章,先粗略讲讲我没有采用的方法:

  • EnvironmentPlugin引入process.env,这样就可以在业务代码中靠process.env.NODE_ENV来判断了。
  • ProvidePlugin来控制在不同环境里加载不同的配置文件(业务代码用的)。

那我用的是什么方法呢?我最后选用的是DefinePlugin

举个官方例子,其大概用法是这样的:

new webpack.DefinePlugin({
    PRODUCTION: JSON.stringify(true),
    VERSION: JSON.stringify("5fa3b9"),
    BROWSER_SUPPORTS_HTML5: true,
    TWO: "1+1",
    "typeof window": JSON.stringify("object")
})

DefinePlugin可能会被误认为其作用是在webpack配置文件中为编译后的代码上下文环境设置全局变量,但其实不然。它真正的机制是:DefinePlugin的参数是一个object,那么其中会有一些key-value对。在webpack编译的时候,会把业务代码中没有定义(使用var/const/let来预定义的)而变量名又与key相同的变量(直接读代码的话的确像是全局变量)替换成value。例如上面的官方例子,PRODUCTION就会被替换为trueVERSION就会被替换为'5fa3b9'(注意单引号);BROWSER_SUPPORTS_HTML5也是会被替换为trueTWO会被替换为1+1(相当于是一个数学表达式);typeof window就被替换为'object'了。

再举个例子,比如你在代码里是这么写的:

if (!PRODUCTION)
    console.log('Debug info')
if (PRODUCTION)
    console.log('Production log')

那么在编译生成的代码里就会是这样了:

if (!true)
    console.log('Debug info')
if (true)
    console.log('Production log')

而如果你用了UglifyJsPlugin,则会变成这样:

console.log('Production log')

如此一来,只要在俩环境的配置文件里用DefinePlugin分别定义好IS_PRODUCTION的值,我们就可以在业务代码里进行判断了:

  /* global IS_PRODUCTION:true */
  if (!IS_PRODUCTION) {
    console.log('如果你看到这个Log,那么这个版本实际上是开发用的版本');
  }

需要注意的是,如果你在webpack里整合了ESLint,那么,由于ESLint会检测没有定义的变量(ESLint要求使用全局变量时要用window.xxxxx的写法),因此需要一个global注释声明(/* global IS_PRODUCTION:true */)IS_PRODUCTION是一个全局变量(当然在本例中并不是)来规避warning。

示例代码

诸位看本系列文章,搭配我在Github上的脚手架项目食用更佳哦(笑):Array-Huang/webpack-seed(https://github.com/Array-Huang/webpack-seed)

附系列文章目录(同步更新)

本文首发于Array_Huang的技术博客——实用至上,非经作者同意,请勿转载。
原文地址:https://segmentfault.com/a/1190000006952432
如果您对本系列文章感兴趣,欢迎关注订阅这里:https://segmentfault.com/blog/array_huang

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

推荐阅读更多精彩内容