前端网站脚手架:目前开发中的版本可能无法正常运行,如果遇到请使用tags(releases)中的稳定版本
npx fe-site your-project
cd your-project具体见
package.json中的scripts选项、
npm run dev: 会启动一个本地的dev server, 并且实时构建。npm run build-uat: 构建uat环境。npm run build-prod: 构建prod环境。
-------------
|----config: 构建配置
|----doc: 文档相关
|----public: 构建过程中直接拷贝而不编译的文件,里面的文件默认会直接输出到构建后的根目录。
|----script: cli脚本
|----src: 编译源码,webpack打包的contextprocess.env.NODE_ENVornodeEnv: 构建的环境,很多构建工具会使用此变量,比如开发为development,测试为test,发布生产为production。process.env.APP_ENVorappEnv: 表示项目发布的目的,因为项目可能会发布多个生产环境(NODE_ENV都为production),但是又有些差别,比如调用的API不同,最常见的场景是预发布uat和正式发布prod的区别。root: 项目根目录。context: webpack构建上下文。
为了方便开发,内置了一个简单的cli工具:node ./script/cli.js, 命令如下:
node ./script/cli.js build -a dev -n development -r ./ -s: 执行构建。
其中:
-nor-node-env: 构建环境,development | production, 默认production。-aor-app-env: 发布环境类别,比如uat,prod等,默认prod。-ror-root: 项目根目录,默认执行命令的目录process.cwd(),在根目录之外的地方执行构建才需要这个选项。-sor-server: 是否启动webpack-dev-server,默认false。
npx browserslistconfig/env/${env}.conf.js
构建配置都放在config目录下,配置方式:
// envArgs = { appEnv, nodeEnv, root }
module.exports = (envArgs) => {
return {
// define constants
define: {
API_BASEURL: JSON.stringify(`https://api.${envArgs.appEnv}.dev.com/`)
}
};
};合并优先级:config/env/${env}.conf.js > config/com.conf.js > 默认配置。
配置合并策略:一级属性进行合并,二级属性进行替换,比如
config/env/${env}.conf.js
{
base: {},
define: { a: 1 }
}config/com.conf.js
{
base: { context: './' },
define: { b: 2 }
}合并后
{
base: { context: './' },
define: { a: 1 }
}默认配置:
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const path = require('path');
// 国际化配置
const i18nConfig = {
// 开启国际化
on: true,
// 语言列表
languages: ['zh-CN', 'en-US'],
// 映射语言
map: {
zh: 'zh-CN',
en: 'en-US'
},
// 检查语言的顺序
detects: [
// /zh-CN/xxx
'path',
// /xxx?lang=zh-CN
'query',
// localStorage.getItem('lang')
'store',
// navigator.language: zh-CN
'browser'
],
// 默认语言
default: 'zh-CN'
};
module.exports = (envArgs) => {
const isProduction = envArgs.nodeEnv === 'production';
return {
// base
base: {
context: path.resolve(envArgs.root, './src/')
},
// define constants
define: {
PUBLIC_PATH: JSON.stringify('/'),
GLOBAL_DATA: JSON.stringify({ appEnv: envArgs.appEnv }),
API_BASEURL: JSON.stringify(`https://api.${envArgs.appEnv}.com/`),
'process.env.NODE_ENV': JSON.stringify(envArgs.nodeEnv),
'process.env.APP_ENV': JSON.stringify(envArgs.appEnv)
},
// output settings
output: {
// 输出目录
distPath: path.join(envArgs.root, './dist'),
// public path
publicPath: '/',
// 是否输出source map
sourceMap: envArgs.nodeEnv !== 'production',
// 是否压缩
compress: envArgs.nodeEnv === 'production',
// 是否清理产出目录distPath
clear: envArgs.nodeEnv !== 'production',
// mode
mode: envArgs.nodeEnv === 'production' ? 'production' : 'development',
// 是否保存stats.json,以供后续分析
statsJson: true,
// split chunks
// splitChunks: null,
splitChunks: {
chunks: 'all',
minSize: 30000,
maxSize: 900000,
minChunks: 1,
maxAsyncRequests: 5,
maxInitialRequests: 4,
automaticNameDelimiter: '-',
name: !isProduction,
cacheGroups: {
react: {
test: /[\\/]node_modules[\\/](react|react-dom|react-router|react-router-dom|prop-types)[\\/]/,
priority: 2,
name: 'vendor-rect',
reuseExistingChunk: true
},
vue: {
test: /[\\/]node_modules[\\/](vue|vue-router|vuex)[\\/]/,
priority: 2,
name: 'vendor-vue',
reuseExistingChunk: true
},
rxjs: {
test: /[\\/]node_modules[\\/](rxjs)[\\/]/,
priority: 2,
name: 'vendor-rxjs',
reuseExistingChunk: true
}
}
}
},
// entry
entry: {
// js entry
js: {
'entry/index': './entry/index.js',
'entry/vue-app': './entry/vue-app.js',
'entry/rc-app': './entry/rc-app.jsx'
},
// html
html: [
{
template: 'html/index.html',
filename: 'index.html',
chunks: ['entry/index'],
inject: false,
minify: false,
data: {
publicPath: '/',
i18nConfig
}
},
{
template: 'html/vue-app.html',
filename: 'vue-app.html',
chunks: ['entry/vue-app'],
inject: false,
minify: false,
data: {
publicPath: '/',
i18nConfig
}
},
{
template: 'html/rc-app.html',
filename: 'rc-app.html',
chunks: ['entry/rc-app'],
inject: false,
minify: false,
data: {
publicPath: '/',
i18nConfig
}
}
]
},
// loaders
loader: {
rules: [
{
test: /html\/.*\.html$/,
use: [
{
loader: 'ejs-loader'
},
{
loader: 'extract-loader'
},
{
loader: 'html-loader',
options: {
attributes: {
list: [
{
tag: 'img',
attribute: 'src',
type: 'src'
}
]
},
minimize: {
removeComments: false,
collapseWhitespace: false,
removeAttributeQuotes: false
}
}
}
]
},
{
test: /\.vue$/,
use: 'vue-loader'
},
{
test: /\.less$/,
use: (envArgs.nodeEnv === 'production'
? [MiniCssExtractPlugin.loader]
: [
{
loader: 'style-loader'
}
]
).concat([
{
loader: 'css-loader',
options: {
importLoaders: 0,
sourceMap: !isProduction
}
},
{
loader: 'postcss-loader',
options: {
ident: 'postcss',
plugins: (loader) => [require('autoprefixer')()],
sourceMap: !isProduction
}
},
{
loader: 'less-loader',
options: {
paths: [path.join(envArgs.root, 'node_modules')],
relativeUrls: true,
math: 'parens-division',
strictUnits: true,
sourceMap: !isProduction
}
}
])
},
{
test: /\.scss$/,
use: (envArgs.nodeEnv === 'production'
? [MiniCssExtractPlugin.loader]
: [
{
loader: 'style-loader'
}
]
).concat([
{
loader: 'css-loader',
options: {
importLoaders: 0,
sourceMap: !isProduction
}
},
{
loader: 'postcss-loader',
options: {
ident: 'postcss',
plugins: (loader) => [require('autoprefixer')()],
sourceMap: !isProduction
}
},
{
loader: 'sass-loader',
options: {
sourceMap: !isProduction
}
}
])
},
{
test: /\.styl$/,
use: (envArgs.nodeEnv === 'production'
? [MiniCssExtractPlugin.loader]
: [
{
loader: 'style-loader'
}
]
).concat([
{
loader: 'css-loader',
options: {
importLoaders: 0,
sourceMap: !isProduction
}
},
{
loader: 'postcss-loader',
options: {
ident: 'postcss',
plugins: (loader) => [require('autoprefixer')()],
sourceMap: !isProduction
}
},
{
loader: 'stylus-loader',
options: {
sourceMap: !isProduction
}
}
])
},
{
test: /\.pcss$/,
use: (envArgs.nodeEnv === 'production'
? [MiniCssExtractPlugin.loader]
: [
{
loader: 'style-loader'
}
]
).concat([
{
loader: 'css-loader',
options: {
importLoaders: 0,
sourceMap: !isProduction
}
},
{
loader: 'postcss-loader',
options: {
ident: 'postcss',
plugins: (loader) => [require('autoprefixer')()],
sourceMap: !isProduction
}
}
])
},
{
test: /\.css$/,
use: (envArgs.nodeEnv === 'production'
? [MiniCssExtractPlugin.loader]
: [
{
loader: 'style-loader'
}
]
).concat([
{
loader: 'css-loader',
options: {
importLoaders: 0,
sourceMap: !isProduction
}
},
{
loader: 'postcss-loader',
options: {
ident: 'postcss',
plugins: (loader) => [require('autoprefixer')()],
sourceMap: !isProduction
}
}
])
},
{
test: /\.js$/,
use: [
{
loader: 'babel-loader',
options: {
cacheDirectory: path.resolve(envArgs.root, './.cache'),
configFile: false
}
}
]
},
{
test: /\.jsx$/,
use: [
{
loader: 'babel-loader',
options: {
cacheDirectory: path.resolve(envArgs.root, './.cache'),
configFile: false
}
}
]
},
{
test: /\.(ico|png|jpe?g|gif|svg)(\?.*)?$/,
loader: 'url-loader',
options: {
limit: 10000,
name: 'imgs/[path][name].[hash].[ext]'
}
},
{
test: /\.(mp3|mp4)(\?.*)?$/,
loader: 'url-loader',
options: {
name: 'medias/[path][name].[hash].[ext]'
}
},
{
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
loader: 'url-loader',
options: {
limit: 10000,
name: 'fonts/[path][name].[hash].[ext]'
}
}
]
},
plugin: {
vue: true
},
public: {
copyList: [
{
from: path.resolve(envArgs.root, './public'),
to: path.resolve(envArgs.root, './dist'),
ignore: ['.*']
}
]
},
i18n: i18nConfig,
// dev server
// https://webpack.js.org/configuration/dev-server/
devServer: {
port: 3003,
contentBase: path.join(envArgs.root, './dist'),
writeToDisk: true,
headers: {
'Access-Control-Expose-Headers': 'Token',
'Access-Control-Allow-Headers': 'Token,Locale,Content-Type',
'Access-Control-Allow-Origin': '*'
},
proxy: {
'/xxx-api': 'http://localhost:3000'
}
}
};
};