源代码 https://github.com/kingov/webpack-learning
通常项目会分成三个运行环境:开发人员在本地跑的开发环境(dev)、测试人员用来做黑盒测试的测试环境(test)和线上运行的生产环境(production)。
简单起见,本文只考虑开发环境(dev)和生产环境(prod),测试环境可以自行类比。
综上,webpack的配置需要有两套,同时两套配置必然会存在相同的部分,故新建目录与文件如下图:
同时引入一个库webpack-merge用于合并base config和特定环境的config
cnpm i -D webpack-merge
webpack-config/base.js
module.exports = {
//common config
};
webpack-config/dev.js, webpack-config/prod.js
const webpackMerge = require('webpack-merge');
const base = require('./base');
module.exports = webpackMerge(base, {
//specific config
});
webpack.config.js
const devModule = require('./webpack-config/dev');
const prodModule = require('./webpack-config/prod');
let finalModule = {};
let ENV = process.env.NODE_ENV; //此处变量可由命令行传入
switch (ENV) {
case 'dev':
finalModule = devModule;
break;
case 'prod':
finalModule = prodModule;
break;
default:
break;
}
module.exports = finalModule;
package.json
{
"name": "webpack-demo",
"version": "1.0.0",
"scripts": {
"dev": "cross-env NODE_ENV=dev webpack",
"prod": "cross-env NODE_env=prod webpack"
},
"devDependencies": {
"webpack": "^2.2.0",
"webpack-merge": "^2.6.1"
}
}
由于*unix和windows设置NODE_ENV的语句有所差异,此处用到了一个库cross-env以达到兼容的目的
cnpm i -D cross-env
新建一个src目录用户存放项目源文件,同时在src下新建一个index.js作为打包的入口
webpack-dev-server
安装webpack-dev-server
cnpm i -D webpack-dev-server
配置webpack config
webpack-config/dev.js
...
module.exports = webpackMerge(base, {
entry: process.cwd() + '/src/index.js',
output: {
filename: '[name].bundle.js'
},
devtool: 'eval-source-map' //enable srouce map
});
修改npm scripts
package.json
{
...
"scripts": {
"dev": "cross-env NODE_ENV=dev webpack-dev-server --inline --hot --host 0.0.0.0",
...
}
}
src目录下新建一个index.html作为template,htmlWebpackPlugins会根据这个template生成网站的index.html,同时自动写入bundle依赖。
src下放一个favicon.ico作为网站的icon
安装htmlWebpackPlugins
cnpm i -D html-webpack-plugin
配置webpack config
webpack-config/dev.js
...
const HtmlWebpackPlugin = require('html-webpack-plugin');
...
module.exports = webpackMerge(base, {
...
plugins: [
new HtmlWebpackPlugin({
filename: 'index.html',
template: process.cwd() + '/src/index.html',
favicon: process.cwd() + '/src/index.html'
})
]
...
});
到这一步算是完成了最基本的开发环境配置,命令行执行npm run dev,然后浏览器打开localhost:8080就能看到成果
将npm scripts中的start命令指向npm run dev,这样每次开始开发只需要执行npm start
package.json
{
...
"scripts": {
...
"start": "npm run dev"
...
}
...
}
webpack本身只能处理js模块,如果需要处理其他类型的文件,就需要Loaders进行转换
ES6+支持
ES6+虽然不能直接被浏览器全部识别,但是能用babel转换成ES5代码。
安装babel编译相关依赖
cnpm i -D babel-core babel-preset-latest babel-preset-stage-2 babel-runtime babel-plugin-transform-runtime babel-loader
新建.babelrc文件并写入:
.babelrc
{
"presets": ["latest", "stage-2"],
"plugins": ["transform-runtime"]
}
配置rules
webpack-config/dev.js
module.exports = webpackMerge(base, {
...
module: {
rules: [
...
{
test: /\.js$/,
exclude: [/node_modules/],
loader: 'babel-loader'
}
...
]
}
...
});
现如今css预处理器已经成为前端开发的标配,Sass(Scss),Less和Stylus各行其道,个人偏好scss。
PostCss可以作为一个后处理器,实现为css自动添加浏览器前缀等功能
cnpm i -D style-loader css-loader postcss-loader autoprefixer node-sass sass-loader
module.exports = {
plugins: [
require('autoprefixer')({browsers: ['last 2 versions', 'iOS 7', 'Firefox > 20']})
]
};
module.exports = webpackMerge(base, {
...
module: {
rules: [
...
{
test: /\.scss$/,
exclude: [/node_modules/],
use: [
'style-loader',
{
loader: 'css-loader',
options: {
sourceMap: true
}
},
'postcss-loader',
{
loader: 'sass-loader',
options: {
sourceMap: true
}
}
]
}
...
]
}
...
});
cnpm i -D html-loader file-loader url-loader
module.exports = webpackMerge(base, {
...
module: {
rules: [
...
{
test: /\.html$/,
loader: 'html-loader',
options: {
minimize: true
}
},
{
test: /\.(jpe?g|png|gif|svg)$/i,
loader: 'url-loader',
options: {
limit: 10000,
hash: 'sha512',
publicPath: '/',
name: 'assets/images/[hash].[ext]'
}
}
...
]
}
...
});
至此,dev环境配置完成
- entry: process.cwd() + '/src/index.js',
webpack-config/base.js
module.exports = {
entry: process.cwd() + '/src/index.js',
};
webpack-config/prod.js
const webpackMerge = require('webpack-merge');
const base = require('./base');
module.exports = webpackMerge(base, {
output: {
filename: 'bundle.[chunkhash].js',
path: process.cwd() + '/dist'
},
});
-new HtmlWebpackPlugin({
- filename: 'index.html',
- template: process.cwd() + '/src/index.html',
- favicon: process.cwd() + '/src/index.html'
-})
webpack-config/base.js
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
...
plugins: [
new HtmlWebpackPlugin({
filename: 'index.html',
template: process.cwd() + '/src/index.html',
favicon: process.cwd() + '/src/index.html'
})
],
};
-{
- test: /\.js$/,
- exclude: [/node_modules/],
- loader: 'babel-loader'
-}
...
-{
- test: /\.html$/,
- loader: 'html-loader',
- options: {
- minimize: true
- }
-},
-{
- test: /\.(jpe?g|png|gif|svg)$/i,
- loader: 'url-loader',
- options: {
- limit: 10000,
- hash: 'sha512',
- publicPath: '/',
- name: 'assets/images/[hash].[ext]'
- }
-}
webpack-config/base.js
...
module.exports = {
...
module: {
rules: [
{
test: /\.js$/,
exclude: [/node_modules/],
loader: 'babel-loader'
},
{
test: /\.html$/,
loader: 'html-loader',
options: {
minimize: true
}
},
{
test: /\.(jpe?g|png|gif|svg)$/i,
loader: 'url-loader',
options: {
limit: 10000,
hash: 'sha512',
publicPath: '/',
name: 'assets/images/[hash].[ext]'
}
}
]
}
};
cnpm i -D extract-text-webpack-plugin@beta
webpack-config/prod.js
...
const ExtractTextPlugin = require("extract-text-webpack-plugin");
...
module: {
rules: [
{
test: /\.scss$/,
exclude: [/node_modules/],
use: ExtractTextPlugin.extract({
fallback: 'style-loader',
use: [
{
loader: 'css-loader',
options: {
minimize: true
}
},
'postcss-loader',
'sass-loader'
]
})
}
]
},
...
plugins: [
new ExtractTextPlugin({
filename: "bundle.[chunkhash].css"
})
],
cnpm i - D uglifyjs-webpack-plugin
webpack-config/prod.js
...
const UglifyJSPlugin = require('uglifyjs-webpack-plugin');
...
module.exports = webpackMerge(base, {
...
const webpack = require('webpack');
...
plugins: [
...
new UglifyJSPlugin({
compress: {
warnings: false,
},
output: {
comments: false
}
})
...
]
...
})
...
构建前先清空,防止出现垃圾文件
cnpm i -D clean-webpack-plugin
webpack-config/prod.js
...
module.exports = webpackMerge(base, {
...
plugins: [
...
const CleanWebpackPlugin = require('clean-webpack-plugin');
...
new CleanWebpackPlugin(['dist'], {
root: process.cwd(),
exclude: []
})
...
]
...
})
...
至此,production环境配置完毕,同时抽出了公共部分
开发的时候如果有一个很深的目录比如:src/a/b/c/d/, 然后在d目录下的一个模块需要引入a目录下的模块,需要这样写:import ‘../../../some-module’,为了方便可以配置一个为src目录配置一个alias,这样模块引入只需要这样写:import src/a/some-module。
webpack-config/base.js
...
const path = require('path');
...
module.exports = {
...
resolve: {
extensions: ['.js'],
alias: {
src: path.resolve(__dirname, './../src')
}
}
...
};
export default {
version: '1.0.0'
}
webpack-config/dev.js
import base from './base'
export default {
...base,
env: 'dev'
}
webpack-config/prod.js
import base from './base'
export default {
...base,
env: 'prod'
}
...
const path = require('path');
...
module.exports = webpackMerge(base, {
...
resolve: {
alias: {
config: path.resolve(__dirname, './../src/config/dev.js')
}
}
...
})
...
webpack-config/prod.js
...
const path = require('path');
...
module.exports = webpackMerge(base, {
...
resolve: {
alias: {
config: path.resolve(__dirname, './../src/config/prod.js')
}
}
...
})
...