webpack 生产环境构建
背景
在项目开发的时候,我们通常会将程序分为开发环境和生产环境,开发环境通常指的是我们正在开发的这个阶段所需要的一些环境配置,也就是方便我们开发人员调试开发的一种环境;生产环境通常
是指我们将程序开发完成后经过测试之后无明显异常准备发布上线的环境,也就是用户可以正常使用的就是生产环境;开发环境(development)和生产环境(production)的构建目标差异很大。
开发环境的需求
- 模块热更新(本地开启服务,实时更新)
- sourceMap(方便打包调试)
- 接口代理(配置ProxyTable解决开发环境中的跨域问题)
- 代码规范检查(代码规范检查工具)
生产环境的需求
- 更小的bundle,更轻量的source map,以及更优化的资源,已改善加载时间
- 提取公共代码
- 压缩混淆(压缩混淆代码,清除代码空格,注释等信息使其变得难以阅读)
- 文件压缩/base64编码(压缩代码,减少线上环境文件包的大小)
- 去除无用的代码
开发环境和生产环境的共同需求
- 同样的入口
- 同样的代码处理(loader处理)
- 同样的配置解析
我们需要为不同的开发环境编写彼此独立的webpack配置,还需要一个符合两个环境的通用配置,遵循DRP原则,我们可以使用webpack-merge的工具将这些配置合并在一起
webpack-merge
webpack-merge 为webpack设计的合并
webpack-merge提供了连接数组并合并对象以创建新对象的merge函数。如果遇到函数,它将执行它们,通过算法运行结果,然后将返回的值再次包装在函数中。
尽管此行为具有超出其用途,但在配置webpack时特别有用。每当你需要合并配置对象时,webpack-merge都会派上用场。
还有一个特定于webpack的合并变体,merge.smart该变体能够考虑到webpack的特定情况(及,他可以市价在程序定义变平)
process.env.NODE_ENV
在node中,有全局变量process,表示的是当前的node进程。process.env包含着敢于系统环境的细心。但是process.env中并不存在NODE_ENV这个
东西。NODE_ENV是yoghurt自定义的变量,在webpack中它的用途是判断生产环境或开发环境的依据的
为了查看process对象,我们可以新建一个process.js 内容为console.log(process)
,然后运行node process.js即可
process {
title: 'C:\Windows\System32\cmd.exe - node webpack.prod.js',
version: 'v8.10.0',
moduleLoadList:
[ 'Binding contextify',
'Binding natives',
'Binding config',
'NativeModule events',
'Binding async_wrap',
'Binding icu',
'NativeModule util',
'NativeModule internal/errors',
'NativeModule internal/encoding',
'NativeModule internal/util',
'Binding util',
'Binding constants',
'NativeModule internal/util/types',
'Binding buffer',
'NativeModule buffer',
'NativeModule internal/buffer',
'Binding uv',
'NativeModule internal/process',
'NativeModule internal/process/warning',
'NativeModule internal/process/next_tick',
'NativeModule internal/async_hooks',
'NativeModule internal/process/promises',
'NativeModule internal/process/stdio',
'Binding performance',
'NativeModule perf_hooks',
'NativeModule internal/linkedlist',
'NativeModule internal/trace_events_async_hooks',
'Binding trace_events',
'NativeModule async_hooks',
'NativeModule internal/inspector_async_hook',
'Binding inspector',
'NativeModule timers',
'Binding timer_wrap',
'NativeModule assert',
'NativeModule module',
'NativeModule internal/module',
'NativeModule internal/url',
'NativeModule internal/querystring',
'NativeModule querystring',
'Binding url',
'NativeModule vm',
'NativeModule fs',
'NativeModule path',
'Binding fs',
'NativeModule stream',
'NativeModule internal/streams/legacy',
'NativeModule _stream_readable',
'NativeModule internal/streams/BufferList',
'NativeModule internal/streams/destroy',
'NativeModule _stream_writable',
'NativeModule _stream_duplex',
'NativeModule _stream_transform',
'NativeModule _stream_passthrough',
'Binding fs_event_wrap',
'NativeModule internal/fs',
'NativeModule internal/loader/Loader',
'NativeModule internal/loader/ModuleWrap',
'Internal Binding module_wrap',
'NativeModule internal/loader/ModuleMap',
'NativeModule internal/loader/ModuleJob',
'NativeModule internal/safe_globals',
'NativeModule internal/loader/ModuleRequest',
'NativeModule url',
'NativeModule internal/loader/search',
'NativeModule console',
'Binding tty_wrap',
'NativeModule tty',
'NativeModule net',
'NativeModule internal/net',
'Binding cares_wrap',
'Binding tcp_wrap',
'Binding pipe_wrap',
'Binding stream_wrap',
'NativeModule dns',
'NativeModule readline',
'NativeModule string_decoder',
'NativeModule internal/readline',
'Binding signal_wrap',
'NativeModule constants' ],
versions:
{ http_parser: '2.7.0',
node: '8.10.0',
v8: '6.2.414.50',
uv: '1.19.1',
zlib: '1.2.11',
ares: '1.10.1-DEV',
modules: '57',
nghttp2: '1.25.0',
openssl: '1.0.2n',
icu: '60.1',
unicode: '10.0',
cldr: '32.0',
tz: '2017c' },
arch: 'x64',
platform: 'win32',
release:
{ name: 'node',
lts: 'Carbon',
sourceUrl: 'https://nodejs.org/download/release/v8.10.0/node-v8.10.0.tar.gz',
headersUrl: 'https://nodejs.org/download/release/v8.10.0/node-v8.10.0-headers.tar.gz',
libUrl: 'https://nodejs.org/download/release/v8.10.0/win-x64/node.lib' },
argv:
[ 'D:\nodejs\node.exe',
'C:\Users\cvallis\Desktop\webpackDemo\demo\buildProduction\webpack.prod.js' ],
execArgv: [],
env:
{ ALLUSERSPROFILE: 'C:\ProgramData',
APPDATA: 'C:\Users\cvallis\AppData\Roaming',
'asl.log': 'Destination=file',
CommonProgramFiles: 'C:\Program Files\Common Files',
'CommonProgramFiles(x86)': 'C:\Program Files (x86)\Common Files',
CommonProgramW6432: 'C:\Program Files\Common Files',
COMPUTERNAME: 'DESKTOP-PH9H8R1',
ComSpec: 'C:\WINDOWS\system32\cmd.exe',
hdf5_dlls: 'C:\Windows\System32',
HOMEDRIVE: 'C:',
HOMEPATH: '\Users\cvallis',
'IntelliJ IDEA': 'D:\IntelliJIDEA018.3.1\bin;',
LOCALAPPDATA: 'C:\Users\cvallis\AppData\Local',
LOGONSERVER: '\\DESKTOP-PH9H8R1',
NUMBER_OF_PROCESSORS: '4',
OneDrive: 'C:\Users\cvallis\OneDrive',
OS: 'Windows_NT',
Path: 'D:\Ruby24-x64\bin;C:\WINDOWS\system32;C:\WINDOWS;C:\WINDOWS\System32\Wbem;C:\WINDOWS\System32\WindowsPowerShell\v1.0\;D:\Python软件\mysql-5.7.18-winx64\bin;C;\Program Files (x86)\Windows Kits\8.1\Windows Performance Toolkit\;C:\Windows\System32;D:\nodejs\;D:\Microsoft VS Code\bin;D:\Git\cmd;C:\Users\cvallis\AppData\Local\Programs\Python\Python35\Scripts\;C:\Users\cvallis\AppData\Local\Programs\Python\Python35\;C:\Users\cvallis\AppData\Local\Microsoft\WindowsApps;C:\Users\cvallis\AppData\Roaming\npm;D:\IntelliJIDEA018.3.1\bin',
PATHEXT: '.COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC;.RB;.RBW',
PROCESSOR_ARCHITECTURE: 'AMD64',
PROCESSOR_IDENTIFIER: 'Intel64 Family 6 Model 78 Stepping 3, GenuineIntel',
PROCESSOR_LEVEL: '6',
PROCESSOR_REVISION: '4e03',
ProgramData: 'C:\ProgramData',
ProgramFiles: 'C:\Program Files',
'ProgramFiles(x86)': 'C:\Program Files (x86)',
ProgramW6432: 'C:\Program Files',
PROMPT: '$P$G',
PSModulePath: 'C:\WINDOWS\system32\WindowsPowerShell\v1.0\Modules\',
PUBLIC: 'C:\Users\Public',
SystemDrive: 'C:',
SystemRoot: 'C:\WINDOWS',
TEMP: 'C:\Users\cvallis\AppData\Local\Temp',
TMP: 'C:\Users\cvallis\AppData\Local\Temp',
USERDOMAIN: 'DESKTOP-PH9H8R1',
USERDOMAIN_ROAMINGPROFILE: 'DESKTOP-PH9H8R1',
USERNAME: 'cvallis',
USERPROFILE: 'C:\Users\cvallis',
VS140COMNTOOLS: 'C:\Program Files (x86)\Microsoft Visual Studio 14.0\Common7\Tools\',
windir: 'C:\WINDOWS' },
pid: 31272,
features:
{ debug: false,
uv: true,
ipv6: true,
tls_npn: true,
tls_alpn: true,
tls_sni: true,
tls_ocsp: true,
tls: true },
ppid: 32796,
execPath: 'D:\nodejs\node.exe',
debugPort: 9229,
_startProfilerIdleNotifier: [Function: _startProfilerIdleNotifier],
_stopProfilerIdleNotifier: [Function: _stopProfilerIdleNotifier],
_getActiveRequests: [Function: _getActiveRequests],
_getActiveHandles: [Function: _getActiveHandles],
reallyExit: [Function: reallyExit],
abort: [Function: abort],
chdir: [Function],
cwd: [Function],
umask: [Function: umask],
_kill: [Function: _kill],
_debugProcess: [Function: _debugProcess],
_debugPause: [Function: _debugPause],
_debugEnd: [Function: _debugEnd],
hrtime: [Function: hrtime],
cpuUsage: [Function: cpuUsage],
dlopen: [Function: dlopen],
uptime: [Function: uptime],
memoryUsage: [Function: memoryUsage],
binding: [Function: binding],
_linkedBinding: [Function: _linkedBinding],
_setupDomainUse: [Function: _setupDomainUse],
_events:
{ newListener: [Function],
removeListener: [Function],
warning: [Function],
SIGWINCH: [ [Function], [Function] ] },
_rawDebug: [Function],
_eventsCount: 4,
domain: null,
_maxListeners: undefined,
_fatalException: [Function],
_exiting: false,
assert: [Function],
config:
{ target_defaults:
{ cflags: [],
default_configuration: 'Release',
defines: [],
include_dirs: [],
libraries: [] },
variables:
{ asan: 0,
coverage: false,
debug_devtools: 'node',
debug_http2: false,
debug_nghttp2: false,
force_dynamic_crt: 0,
host_arch: 'x64',
icu_data_file: 'icudt60l.dat',
icu_data_in: '..\..\deps/icu-small\source/data/in\icudt60l.dat',
icu_endianness: 'l',
icu_gyp_path: 'tools/icu/icu-generic.gyp',
icu_locales: 'en,root',
icu_path: 'deps/icu-small',
icu_small: true,
icu_ver_major: '60',
node_byteorder: 'little',
node_enable_d8: false,
node_enable_v8_vtunejit: false,
node_install_npm: true,
node_module_version: 57,
node_no_browser_globals: false,
node_prefix: '/usr/local',
node_release_urlbase: 'https://nodejs.org/download/release/',
node_shared: false,
node_shared_cares: false,
node_shared_http_parser: false,
node_shared_libuv: false,
node_shared_nghttp2: false,
node_shared_openssl: false,
node_shared_zlib: false,
node_tag: '',
node_use_bundled_v8: true,
node_use_dtrace: false,
node_use_etw: true,
node_use_lttng: false,
node_use_openssl: true,
node_use_perfctr: true,
node_use_v8_platform: true,
node_without_node_options: false,
openssl_fips: '',
openssl_no_asm: 0,
shlib_suffix: 'so.57',
target_arch: 'x64',
v8_enable_gdbjit: 0,
v8_enable_i18n_support: 1,
v8_enable_inspector: 1,
v8_no_strict_aliasing: 1,
v8_optimized_debug: 0,
v8_promise_internal_field_count: 1,
v8_random_seed: 0,
v8_trace_maps: 0,
v8_use_snapshot: true,
want_separate_host_toolset: 0 } },
emitWarning: [Function],
nextTick: [Function: nextTick],
_tickCallback: [Function: _tickCallback],
_tickDomainCallback: [Function: _tickDomainCallback],
stdout: [Getter],
stderr: [Getter],
stdin: [Getter],
openStdin: [Function],
exit: [Function],
kill: [Function],
_immediateCallback: [Function: processImmediate],
argv0: 'node',
mainModule:
Module {
id: '.',
exports: {},
parent: null,
filename: 'C:\Users\cvallis\Desktop\webpackDemo\demo\buildProduction\webpack.prod.js',
loaded: false,
children: [ [Object], [Object], [Object], [Object], [Object] ],
paths:
[ 'C:\Users\cvallis\Desktop\webpackDemo\demo\buildProduction\node_modules',
'C:\Users\cvallis\Desktop\webpackDemo\demo\node_modules',
'C:\Users\cvallis\Desktop\webpackDemo\node_modules',
'C:\Users\cvallis\Desktop\node_modules',
'C:\Users\cvallis\node_modules',
'C:\Users\node_modules',
'C:\node_modules' ] } }
如上我们可以看到process是node的全局变量,并且process有env这个属性,但是没有NODE_ENV这个属性
DefinePlugin
DefinePlugin允许创建一个在编译时可以配置的全局常量。这可能会对开发模式和发布模式的构建允许不同的行为非常有用。如果在开发构建中,
而不再发布构建中执行日志记录,则可以使用全局常量来决定是否记录日志。这就是DefinePlugin的用处,设置它,就可以忘记开发和发布构建的规则
用法
每个传进DefinePlugin的键值都是一个标识符或者多个用.连接起来的标识符
- 如果这个值是一个字符串,他会当做一个代码片段来使用
- 如果这个值不是字符串,他会被转化为字符串(包括函数)
- 如果这个值是一个对象,它所有的key会被同样的方式定义
- 如果在一个key前面加了typeof,他会被定义为typeof调用
模式(mode)
提供mode配置选项,告知webpack使用响应的内置优化
支持一下字符串值:
development:会将process.env.NODE_ENV的值设为development。启用NamedChunksPlugin和NamedModulesPlugin
production:会将process.env.NODE_ENV的值设置为production。启用FlagDependencyUsagePlugin,FlagIncludedChunksPlugin,
ModuleConcatenationPlugin,NoEmitOnErrorsPlugin,OccurrenceOrderPlugin,SideEfffectsFlagPlugin和UglifyJsPlugin
记住,只设置NODE_ENV,则不会自动设置mode
split css
extract-text-webpack-plugin
作用:该插件的主要是为了抽离css样式,防止将样式打包在js中引起页面样式加载错乱的现象
extract-text-webpack-plugin
mini-text-extract-plugin
该插件将css提取到单独的文件中。它为每个包含css的JS文件创建一个css文件。它支持css和SourceMap的按需加载。
mini-text-extract-plugin
postcss
postCSS是由插件来起作用的,使用那个功能,就要安装那个插件,如果只安装了postcss-loader,它并不起作用,还要安装对应的插件autoprefixer等等,安装完成以后,就要进行配置。
对于纯css来说,我们最先使用postcss-loader。需要对postcss-loader进行配置,配置的方式有两种,一种是在webpack的配置文件中,一个是单独给postcss写一个配置文件
实例
文件目录如下图所示
- index.js
import * as Math from "./math.js";
import "./20191031153457.png";
import "./reset.css";
//import "./style.scss";
console.log(Math)
function component(){
var element = document.createElement("pre");
element.innerHTML = [
"hello webpack!",
"5 cubed is equal to " + Math.cube(5)
].join("
");
return element
}
document.body.appendChild(component())
console.log(process.env.NODE_ENV)
- math.js
export function add(a,b){
return a + b;
}
export function minus(a,b){
return a - b;
}
export function multiple(a,b){
return a * b;
}
export function divide(a,b){
return a / b;
}
export function cube(a){
return a*a*a
}
- reset.css
body{
background-color: red;
color: #FFFFFF;
}
/*@import url("./style.scss");*/
- style.scss
body,html{
padding: 0;
margin: 0;
background-color:black ;
color: #FFFFFF;
}
- webpack.common.js
const path = require("path");
module.exports = {
entry:[path.resolve(__dirname,"src/style.scss"),path.resolve(__dirname,"src/index.js")],
output:{
filename:"[name].js",
path:path.resolve(__dirname,"dist")
},
module:{
rules:[
{
test:/.(png|jpe?g|gif)$/,
use:[
{
loader:"url-loader",
options:{
limit:10000
}
}
]
}
]
}
}
- webpack.dev.js
const merge = require("webpack-merge");
const common = require("./webpack.common.js");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const webpack = require("webpack");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
module.exports = merge(common,{
mode:"development",
module:{
rules:[
{
test:/.(sa|sc|c)ss$/,
use:[
"style-loader",
"css-loader",
"sass-loader"
]
}
]
},
plugins:[
new HtmlWebpackPlugin({
title:"webpack 构建生产环境"
}),
new webpack.HotModuleReplacementPlugin()
],
devtool:"inline-source-map",
devServer:{
hot:true,
host:"localhost",
port:"8081"
}
})
- webpack.prod.js
const merge = require("webpack-merge");
const common = require("./webpack.common.js");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const {CleanWebpackPlugin} = require("clean-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const webpack = require("webpack");
module.exports = merge(common,{
mode:"production",
// optimization:{
// splitChunks:{
// chunks:"all"
// }
// },
module:{
rules:[
{
test:/.(css|scss)$/,
use:[
MiniCssExtractPlugin.loader,
{
loader:"css-loader",
options:{
importLoaders:2
}
},
"postcss-loader",
"sass-loader"
]
}
]
},
plugins:[
new CleanWebpackPlugin(),
new MiniCssExtractPlugin({
filename:"[name].[hash].css",
chunkFilename:"[id].[hash].css",
allChunks: true,
}),
new HtmlWebpackPlugin({
title:"webpack 生产环境构建"
})
],
devtool:"source-map"
})
- package.json
{
"name": "webpackDevServer",
"sideEffects": [
"*.css",
".scss"
],
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo "Error: no test specified" && exit 1",
"dev": "webpack-dev-server --config demo/webpack-dev-server/webpack-dev-server.js",
"server": "node demo/webpack-dev-middleware/server.js",
"hmr": "webpack-dev-server --config demo/HMR/webpack.hmr.js",
"treeShaking:dev": "webpack-dev-server --config demo/tree-shaking/tree-shaking.js",
"treeShaking:build": "webpack --config demo/tree-shaking/tree-shaking.js",
"buildProd:dev": "webpack-dev-server --config demo/buildProduction/webpack.dev.js",
"buildProd:build": "webpack --config demo/buildProduction/webpack.prod.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"@babel/core": "7.6.4",
"@babel/plugin-transform-runtime": "^7.6.2",
"@babel/preset-env": "^7.6.3",
"autoprefixer": "^9.7.1",
"babel-loader": "8.0.6",
"clean-webpack-plugin": "3.0.0",
"css-loader": "^3.2.0",
"express": "4.17.1",
"file-loader": "^4.2.0",
"html-webpack-plugin": "3.2.0",
"mini-css-extract-plugin": "^0.8.0",
"node-sass": "^4.13.0",
"postcss-import": "^12.0.1",
"postcss-loader": "^3.0.0",
"postcss-url": "^8.0.0",
"sass-loader": "8.0.0",
"style-loader": "^1.0.0",
"url-loader": "^2.2.0",
"webpack": "4.41.2",
"webpack-cli": "3.3.9",
"webpack-dev-middleware": "3.7.2",
"webpack-dev-server": "3.8.2",
"webpack-merge": "^4.2.2"
}
}
执行npm run buildProd:dev
结果如下
执行npm run buildProd:build
结果如下
遇到的问题:
- 在生产环境中分离css的时候,用extract-text-webpack-plugin,webpack4不支持,推荐用mini-text-extract-plugin
- 当我在index.js中
import "./style.scss";
打包的时候并没有打包对应的css文件,查看了mini-text-extract-plugin的相关资料,该插件将
css提取到单独的文件中。它为每个包含css的js文件创建一个css文件。它支持css和sourceMap的按需加载,最后解决是在webpack配置文件webpack.common.js
中将其引入在entry中