前言
弄完了前后端分离,我们自然想打包发布项目了。
不多说,就让我们来看看吧。
开发
直接上代码:
const webpack = require('webpack')
const path = require('path')
const ExtractTextPlugin = require('extract-text-webpack-plugin')
const webpackConfigBase = require('./webpack.config.js')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const CleanWebpackPlugin = require('clean-webpack-plugin')
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
const exec = require('child_process').execSync
const pkg = require('./package.json')
// 为了抽离出两份CSS,创建两份ExtractTextPlugin
// base作为基础的css,基本不变,所以,可以抽离出来充分利用浏览器缓存
// app作为迭代的css,会经常改变
const extractBaseCSS = new ExtractTextPlugin({filename:'static/css/base.[chunkhash:8].css', allChunks: true})
const extractAppCSS = new ExtractTextPlugin({filename:'static/css/app.[chunkhash:8].css', allChunks: true})
// 减少路径书写
function resolve(dir) {
return path.join(__dirname, dir)
}
// 网站图标配置
const favicon = resolve('favicon.ico')
// 网站版本号设置
let appVersion = ''
try {
appVersion = exec('git rev-parse --short HEAD').toString().replace(/\n/, '')
} catch (e) {
console.warn('Getting revision FAILED. Maybe this is not a git project.')
}
const config = Object.assign(webpackConfigBase, {
// You should configure your server to disallow access to the Source Map file for normal users!
devtool: 'source-map',
entry: {
app: resolve('app/index.js'),
// 将第三方依赖(node_modules)的库打包,从而充分利用浏览器缓存
vendor: Object.keys(pkg.dependencies)
},
output: {
path: resolve('dist'),
// publicPath: 'https://cdn.self.com'
publicPath: resolve('dist/'),
filename: 'static/js/[name].[chunkhash:8].js'
},
module: {
rules: [
{
test: /\.js$/,
include: [resolve('app')],
loader: 'babel-loader'
},
{
test: /\.vue$/,
exclude: /node_modules/,
loader: 'vue-loader',
options: {
extractCSS: true,
loaders: {
scss: extractAppCSS.extract({
fallback: 'vue-style-loader',
use: [
{
loader: 'css-loader',
options: {
sourceMap: true
}
},
{
loader: 'postcss-loader',
options: {
sourceMap: true
}
},
{
loader: 'sass-loader',
options: {
sourceMap: true
}
}
]
})
}
}
},
{
test: /\.(css|scss)$/,
use: extractBaseCSS.extract({
fallback: 'style-loader',
use: [
{
loader: 'css-loader',
options: {
sourceMap: true
}
},
{
loader: 'postcss-loader',
options: {
sourceMap: true
}
},
{
loader: 'sass-loader',
options: {
sourceMap: true
}
}
]
})
},
{
test: /\.(png|jpe?g|gif|svg|ico)(\?.*)?$/,
loader: 'url-loader',
options: {
limit: 8192,
name: 'static/img/[name].[hash:8].[ext]'
}
},
{
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
loader: 'url-loader',
options: {
limit: 8192,
name: 'static/font/[name].[hash:8].[ext]'
}
}
]
},
plugins: [
// Scope hosting
new webpack.optimize.ModuleConcatenationPlugin(),
// 删除build文件夹
new CleanWebpackPlugin(
resolve('dist')
),
// 抽离出css
extractBaseCSS,
extractAppCSS,
// 提供公共代码vendor
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
filename: 'static/js/[name].[chunkhash:8].js'
}),
// html 模板插件
new HtmlWebpackPlugin({
appVersion,
favicon,
filename: 'index.html',
template: resolve('app/index.html'),
minify: {
removeComments: true,
collapseWhitespace: false
}
}),
// 定义全局常量
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: '"production"'
}
}),
// 可视化分析
new BundleAnalyzerPlugin(),
// 加署名
new webpack.BannerPlugin('Copyright by 子咻 https://github.com/CodeLittlePrince/blog'),
]
})
module.exports = config
代码几乎全都有注释,有不懂的可以在评论去留言。
思考
虽然代码写好了,但是我们不禁发出一声“卧槽”,好多和webpack.config.js一样的代码啊,要是改了一样的代码部分,我还得同时改两份,而且,这么多的冗余代码对于一个优秀的程序员来讲,是不可容忍的。
那我们改怎么呢?
重构webpack的配置代码
一、创建一个基础的webpack配置文件
我们就叫webapck.config.base.js
吧:
const path = require('path')
// 为了抽离出两份CSS,创建两份ExtractTextPlugin
// base作为基础的css,基本不变,所以,可以抽离出来充分利用浏览器缓存
// app作为迭代的css,会经常改变
const isProduction = process.env.NODE_ENV === 'production'
const ExtractTextPlugin = require('extract-text-webpack-plugin')
const extractBaseCSS =
new ExtractTextPlugin(
{
filename:'static/css/base.[chunkhash:8].css',
allChunks: true,
disable: !isProduction // 开发环境下不抽离css
}
)
const extractAppCSS
= new ExtractTextPlugin(
{
filename:'static/css/app.[chunkhash:8].css',
allChunks: true,
disable: !isProduction // 开发环境下不抽离css
}
)
// 减少路径书写
function resolve(dir) {
return path.join(__dirname, dir)
}
// 网站图标配置
const favicon = resolve('favicon.ico')
// __dirname: 总是返回被执行的 js 所在文件夹的绝对路径
// __filename: 总是返回被执行的 js 的绝对路径
// process.cwd(): 总是返回运行 node 命令时所在的文件夹的绝对路径
const config = {
resolve: {
// 扩展名,比如import 'app.vue',扩展后只需要写成import 'app'就可以了
extensions: ['.js', '.vue', '.scss', '.css'],
// 取路径别名,方便在业务代码中import
alias: {
api: resolve('app/api/'),
common: resolve('app/common/'),
views: resolve('app/views/'),
components: resolve('app/components/'),
componentsBase: resolve('app/componentsBase/'),
directives: resolve('app/directives/'),
filters: resolve('app/filters/'),
mixins: resolve('app/mixins/')
}
},
// loaders处理
module: {
rules: [
{
test: /\.js$/,
include: [resolve('app')],
loader: [
'babel-loader',
'eslint-loader'
]
},
{
test: /\.vue$/,
exclude: /node_modules/,
loader: 'vue-loader',
options: {
extractCSS: true,
loaders: {
scss: extractAppCSS.extract({
fallback: 'vue-style-loader',
use: [
{
loader: 'css-loader',
options: {
sourceMap: true
}
},
{
loader: 'postcss-loader',
options: {
sourceMap: true
}
},
{
loader: 'sass-loader',
options: {
sourceMap: true
}
}
]
})
}
}
},
{
test: /\.(css|scss)$/,
use: extractBaseCSS.extract({
fallback: 'style-loader',
use: [
{
loader: 'css-loader',
options: {
sourceMap: true
}
},
{
loader: 'postcss-loader',
options: {
sourceMap: true
}
},
{
loader: 'sass-loader',
options: {
sourceMap: true
}
}
]
})
},
{
test: /\.(png|jpe?g|gif|svg|ico)(\?.*)?$/,
loader: 'url-loader',
options: {
limit: 8192,
name: isProduction
? 'static/img/[name].[hash:8].[ext]'
: 'static/img/[name].[ext]'
}
},
{
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
loader: 'url-loader',
options: {
limit: 8192,
name: isProduction
? 'static/font/[name].[hash:8].[ext]'
: 'static/font/[name].[ext]'
}
}
]
}
}
module.exports = {
config,
favicon,
resolve,
extractBaseCSS,
extractAppCSS
}
二、重构webpack开发环境配置
const webpack = require('webpack')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')
const webpackConfigBase = require('./webpack.config.base.js')
const config = Object.assign(webpackConfigBase.config, {
// sourcemap 模式
devtool: 'cheap-module-eval-source-map',
// 入口
entry: {
app: [
'babel-polyfill', // 这里是配合babel-present-env导入的动态babel-polyfill,因此npm需dev依赖
webpackConfigBase.resolve('app/index.js')
]
},
// 输出
output: {
path: webpackConfigBase.resolve('dev'),
filename: 'index.bundle.js'
},
plugins: [
// html 模板插件
new HtmlWebpackPlugin({
favicon: webpackConfigBase.favicon,
filename: 'index.html',
template: webpackConfigBase.resolve('app/index.html')
}),
// 抽离出css,开发环境其实不抽离,但是为了配合extract-text-webpack-plugin插件,需要做个样子
webpackConfigBase.extractAppCSS,
webpackConfigBase.extractBaseCSS,
// 热替换插件
new webpack.HotModuleReplacementPlugin(),
// 更友好地输出错误信息
new FriendlyErrorsPlugin()
],
devServer: {
proxy: {
// 凡是 `/api` 开头的 http 请求,都会被代理到 localhost:7777 上,由 koa 提供 mock 数据。
// koa 代码在 ./mock 目录中,启动命令为 npm run mock。
'/api': {
target: 'http://localhost:7777', // 如果说联调了,将地址换成后端环境的地址就哦了
secure: false
}
},
host: '0.0.0.0',
port: '9999',
disableHostCheck: true, // 为了手机可以访问
contentBase: webpackConfigBase.resolve('dev'), // 本地服务器所加载的页面所在的目录
// historyApiFallback: true, // 为了SPA应用服务
inline: true, //实时刷新
hot: true // 使用热加载插件 HotModuleReplacementPlugin
}
})
module.exports = config
三、重构webpack开发环境配置
const webpack = require('webpack')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const CleanWebpackPlugin = require('clean-webpack-plugin')
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
const exec = require('child_process').execSync
const webpackConfigBase = require('./webpack.config.base.js')
const pkg = require('./package.json')
// 网站版本号设置
let appVersion = ''
try {
appVersion = exec('git rev-parse --short HEAD').toString().replace(/\n/, '')
} catch (e) {
console.warn('Getting revision FAILED. Maybe this is not a git project.')
}
const config = Object.assign(webpackConfigBase.config, {
// You should configure your server to disallow access to the Source Map file for normal users!
devtool: 'source-map',
entry: {
app: webpackConfigBase.resolve('app/index.js'),
// 将第三方依赖(node_modules)的库打包,从而充分利用浏览器缓存
vendor: Object.keys(pkg.dependencies)
},
output: {
path: webpackConfigBase.resolve('dist'),
// publicPath: 'https://cdn.self.com'
publicPath: webpackConfigBase.resolve('dist/'),
filename: 'static/js/[name].[chunkhash:8].js'
},
plugins: [
// Scope hosting
new webpack.optimize.ModuleConcatenationPlugin(),
// 删除build文件夹
new CleanWebpackPlugin(
webpackConfigBase.resolve('dist')
),
// 抽离出css
webpackConfigBase.extractAppCSS,
webpackConfigBase.extractBaseCSS,
// 提取公共代码vendor
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
filename: 'static/js/[name].[chunkhash:8].js'
}),
// html 模板插件
new HtmlWebpackPlugin({
appVersion,
favicon: webpackConfigBase.favicon,
filename: 'index.html',
template: webpackConfigBase.resolve('app/index.html'),
minify: {
removeComments: true,
collapseWhitespace: false
}
}),
// 定义全局常量
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: '"production"'
}
}),
// 可视化分析
new BundleAnalyzerPlugin(),
// 加署名
new webpack.BannerPlugin('Copyright by 子咻 https://github.com/CodeLittlePrince/blog'),
]
})
module.exports = config
代码瞬间变得清晰、精简、高大上有没有?!(^-^)V
看一下打包处理后代码情况(兼容IE10及以上):
总结
这一篇我们编写了开发环境用的webpack配置文件,然后发现代码的冗余从而重构了开发和发布环境的webpack配置。
之后,我们还需要能够自动测试我们写的业务代码,避免人工手动各种戳页面(虽然大部分公司都是这么干的,即使是大公司会腾出时间和人手写测试用例的部门也不多),不过架构还是要做的。
下篇我们会来完成测试的流程 - 从零开始做Vue前端架构(6)
项目完整代码
Vue前端架构-by 子咻