项目基本结构javascript
config:基本的项目配置,以及打包文件配置css
dist:打包后的文件目录html
public:html,ico等项目出示文件,以及第三方js,css等文件前端
src项目主文件java
.eslintignore,.eslintrc等相关配置一会再说node
webpack.config.common.js主要是开发环境和生成环境均可用到的文件。react
entry: {
app: '../src/index.js'
},
复制代码
定义环境变量webpack
development或者production,定义以后webpack会默认选择部分插件ios
参考官网查看开发以及生成环境的做用git
rule加载是从右到左,从下到上,注意配置。
利用bable-loader解析js,同时利用thread-loade开启多线程解析js
{
test: /\.js?$/,
use: [
{
loader: 'thread-loader',
options: {
workers: 2,
poolTimeout: 2000
}
},
{
loader: 'babel-loader',
options: {
cacheDirectory: true
}
}
],
exclude: /node_modules/,
include: config.appSrc
},
复制代码
在bable解析时,若是没有在options里面配置,或者没有特殊之处,会默认去找咱们本地的babel.config.js文件,如下是我开发时用到的bable解析插件
module.exports = {
env: {
development: {
plugins: ['@babel/plugin-transform-modules-commonjs']
},
test: {
plugins: ['@babel/plugin-transform-modules-commonjs']
}
},
presets: [
[
'@babel/preset-env',
{
useBuiltIns: 'usage',
corejs: 3,
debug: false,
targets: {
node: 'current',
browsers: ['> 1%', 'last 2 versions', 'not ie <= 8']
},
modules: false
}
],
'@babel/preset-react'
],
plugins: [
'react-hot-loader/babel',
[
'@babel/plugin-transform-runtime',
{
corejs: 3
}
],
[
'@babel/plugin-proposal-decorators',
{
legacy: true
}
],
[
'@babel/plugin-proposal-class-properties',
{
loose: true
}
],
'@babel/plugin-proposal-do-expressions',
'@babel/plugin-proposal-export-default-from',
'@babel/plugin-proposal-export-namespace-from',
'@babel/plugin-proposal-function-bind',
'@babel/plugin-proposal-function-sent',
'@babel/plugin-proposal-json-strings',
'@babel/plugin-proposal-logical-assignment-operators',
'@babel/plugin-proposal-nullish-coalescing-operator',
'@babel/plugin-proposal-numeric-separator',
'@babel/plugin-transform-arrow-functions',
'@babel/plugin-proposal-optional-chaining',
[
'@babel/plugin-proposal-pipeline-operator',
{
proposal: 'minimal'
}
],
'@babel/plugin-proposal-throw-expressions',
'@babel/plugin-syntax-dynamic-import',
'@babel/plugin-syntax-import-meta',
[
'import',
{
libraryName: 'antd',
style: true
}
]
],
sourceMaps: true
};
复制代码
代码格式化工具,能够来统一前端代码风格
{
enforce: 'pre', //强制去前面执行 由于loader是从下向上 从右向左执行的
test: /\.js?$/,
use: [
{
loader: 'thread-loader',
options: jsWorkerPool
},
{
loader: 'eslint-loader',
options: {
failOnError: false,
failOnWarning: true, //警告不显示
quiet: true,
cache: true,
fix: false // 是否自动修复
}
}
],
exclude: /node_modules/,
include: config.appSrc
},
复制代码
一样这里会去找.eslintrc.js中的配置这里能够去本身找下Airbnb的配置,有须要的话能够在纸上进行改动。还能够新建.eslintignore文件忽略不想使用代码格式化的工具
threadLoader.warmup(cssWorkerPool, [
'css-loader',
'postcss-loader',
'sass-loader',
'less-loader',
'sass-resources-loader'
]);
threadLoader.warmup(jsWorkerPool, ['babel-loader', 'eslint-loader']);
const cssReg = /\.css$/;
const cssModuleReg = /\.module\.css$/;
const sassModuleReg = /\.module\.(scss|sass)$/;
const sassReg = /\.scss|.sass$/;
const lessModuleReg = /\.module\.less/;
const lessReg = /\.less$/;
const styleLoader = (options = {}) => {
const styleInner = isDev
? {
loader: 'style-loader',
options: {
insert: 'head'
}
}
: {
loader: MiniCssExtractPlugin.loader,
//若是提取到单独文件夹下,记得配置一下publicPath,为了正确的照片css中使用的图片资源
//我的习惯将css文件放在单独目录下
options: {
publicPath: '../../'
}
};
const threeLoaderStatus = isDev && {
loader: 'thread-loader',
options: cssWorkerPool
};
return [
styleInner,
threeLoaderStatus,
{
loader: 'css-loader',
options
},
{
loader: 'postcss-loader',
options: {
ident: 'postcss',
plugins: () => [postcssPresetEnv({})]
}
}
].filter(Boolean);
};
const sassLoader = () => {
return [
'sass-loader',
{
loader: 'sass-resources-loader',
options: {
resources: `${config.appSrc}/commen/styles/variable.scss`
}
}
].filter(Boolean);
};
const lessLoader = (options = {}) => {
return [
{
loader: 'less-loader',
options
},
{
loader: 'sass-resources-loader',
options: {
resources: `${config.appSrc}/commen/styles/variable.less`
}
}
].filter(Boolean);
};
{
oneOf: [
{
test: sassModuleReg,
use: [
...styleLoader({
modules: {
localIdentName: 'local]--[hash:base64:5]'
}
}),
...sassLoader()
],
include: config.appSrc
},
{
test: lessModuleReg,
use: [
...styleLoader({
modules: {
localIdentName: '[local]--[hash:base64:5]'
}
}),
...lessLoader({
javascriptEnabled: true
})
],
include: config.appSrc
},
{
test: sassReg,
use: [...styleLoader(), ...sassLoader()],
include: config.appSrc
},
{
test: lessReg,
use: [
...styleLoader(),
...lessLoader({
javascriptEnabled: true
})
],
include: config.appSrc
},
{
test: cssModuleReg,
use: [
...styleLoader({
modules: {
localIdentName: '[local]--[hash:base64:5]'
}
})
],
include: config.appSrc
},
{
test: cssReg,
use: [...styleLoader()],
include: config.appSrc
},
{
test: lessReg,
use: [
...styleLoader(),
...lessLoader({
// 使用less默认运行时替换配置的@color样式
modifyVars: config.color,
javascriptEnabled: true
})
],
include: /node_modules/
}
]
}
复制代码
使用oneOf找到第一个匹配的规则就不往下匹配了,支持css,less,scss。
支持css_module,前提是使用index.css以及index.module.css进行区别,less,scss同样。
{
test: /\.(png|jpg|jpeg|gif|svg)$/,
use: [
{
loader: 'image-webpack-loader',
options: {
mozjpeg: {
progressive: true,
quality: 65
},
optipng: {
enabled: true
},
pngquant: {
quality: [0.65, 0.9],
speed: 4
},
gifsicle: {
interlaced: false
},
webp: {
quality: 75
}
}
},
{
loader: 'url-loader',
options: {
esModule: false,
limit: 0,
name: 'app/images/[name]_[hash:7].[ext]'
}
}
]
},
{
test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/i,
use: [
{
loader: 'file-loader',
options: {
esModule: false,
limit: 15000,
name: 'app/files/[name]_[hash:7].[ext]'
}
}
]
},
{
test: /\.(woff|woff2|eot|ttf|otf)$/,
use: {
loader: 'file-loader',
options: {
limit: 15000,
esModule: false,
name: 'app/fonts/[name]_[hash:7].[ext]'
}
}
},
复制代码
自动将打包好的js放进html中
new HtmlWebpackPlugin({
title: '',
filename: 'index.html',
template: config.appHtml,
favicon: config.favicon,
inject: true,
minify: {
removeComments: true,
collapseWhitespace: true, //折叠空行
removeAttributeQuotes: true //删除双引号
},
chunksSortMode: 'dependency'
}),
复制代码
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: JSON.stringify(process.env.NODE_ENV)
}
}),
复制代码
这个定义以后会在项目中覆盖mode定义的process.env.NODE_ENV环境变量
new webpack.optimize.RuntimeChunkPlugin({
name: entrypoint => `runtime-${entrypoint.name}`
}),
new webpack.optimize.SplitChunksPlugin({
chunks: 'all',
minSize: 30000,
maxAsyncRequests: 5,
maxInitialRequests: 10,
name: true,
automaticNameDelimiter: '~',
cacheGroups: {
vendor: {
priority: -10,
test: /[\\/]node_modules[\\/]/,
name: 'vendor'
},
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true
}
}
}),
复制代码
分离项目中几乎不会变更的代码,加快编译速度
new BundleAnalyzerPlugin({
// concatenateModules: false,
// 能够是`server`,`static`或`disabled`。
// 在`server`模式下,分析器将启动HTTP服务器来显示软件包报告。
// 在“静态”模式下,会生成带有报告的单个HTML文件。
// 在`disabled`模式下,你可使用这个插件来将`generateStatsFile`设置为`true`来生成Webpack Stats JSON文件。
analyzerMode: 'server',
// 将在“服务器”模式下使用的主机启动HTTP服务器。
analyzerHost: '127.0.0.1',
// 将在“服务器”模式下使用的端口启动HTTP服务器。
analyzerPort: 9119,
// 路径捆绑,将在`static`模式下生成的报告文件。
// 相对于捆绑输出目录。
// reportFilename: 'report.html',
// 模块大小默认显示在报告中。
// 应该是`stat`,`parsed`或者`gzip`中的一个。
// 有关更多信息,请参见“定义”一节。
defaultSizes: 'parsed',
// 在默认浏览器中自动打开报告
openAnalyzer: true,
// 若是为true,则Webpack Stats JSON文件将在bundle输出目录中生成
generateStatsFile: false,
// 若是`generateStatsFile`为`true`,将会生成Webpack Stats JSON文件的名字。
// 相对于捆绑输出目录。
statsFilename: 'stats.json',
// stats.toJson()方法的选项。
// 例如,您可使用`source:false`选项排除统计文件中模块的来源。
// 在这里查看更多选项:https://github.com/webpack/webpack/blob/webpack-1/lib/Stats.js#L21
statsOptions: null,
logLevel: 'info' //日志级别。能够是'信息','警告','错误'或'沉默'。
}),
复制代码
output: {
path: 'dist',
filename: 'app/[name].[hash].bundle.js',
chunkFilename: 'app/[name].[hash].bundle.js',
publicPath: '/'
},
复制代码
devServer: {
host: '0.0.0.0.',
port: '1111',
historyApiFallback: true,
overlay: true,
compress: true,
// publicPath: '/dist/', //能够不写,写的话最好和output.publicPath一致
contentBase: 'dist',
hot: true,
inline: true,
// 默认浏览器
open: true,
disableHostCheck: true,
before(app) {
if (process.env.IS_Mock) {
//进入mock
Mock(app);
}
},
proxy: {},
stats: {
// 添加缓存(但未构建)模块的信息
cached: true,
// 显示缓存的资源(将其设置为 `false` 则仅显示输出的文件)
cachedAssets: true,
// 添加 children 信息
children: true,
// 添加 chunk 信息(设置为 `false` 能容许较少的冗长输出)
chunks: true,
// 将构建模块信息添加到 chunk 信息
chunkModules: true,
// `webpack --colors` 等同于
colors: true,
// 添加 --env information
env: false,
// 添加错误信息
errors: true,
// 添加错误的详细信息(就像解析日志同样)
errorDetails: true,
// 添加 compilation 的哈希值
hash: false,
// 添加构建模块信息
modules: true,
// 当文件大小超过 `performance.maxAssetSize` 时显示性能提示
performance: true,
// 添加时间信息
timings: true,
// 添加警告
warnings: true
}
}
}
复制代码
在端口号比较杂的状况下,考虑使用portfinder自动寻找空余端口
// 自动寻找空余端口
module.exports = new Promise((resolve, reject) => {
// 搜寻可用的端口号
portfinder.basePort = config.port;
portfinder.getPort((err, port) => {
if (err) reject(err);
else {
devConfig.devServer.port = port;
}
resolve(devConfig);
});
});
复制代码
在没有后台数据,进行mock数据前,推荐一个方法,能够在devServer的before方法中,进行统一的数据模拟,能够只用配置环境变量解决mock问题
before(app) {
if (process.env.IS_Mock) {
//进入mock
Mock(app);
}
},
复制代码
可使用npm run mock开启mock环境
output: {
path: 'dist',
filename: 'app/js/[name].[chunkhash:8].js',
chunkFilename: 'app/js/[name].[chunkhash:8].bundle.js'
},
复制代码
清除上次构建的文件
// 配和MiniCssExtractPlugin.loader, 提取css到特定的目录下
new MiniCssExtractPlugin({
filename: 'app/css/[name].[contenthash:8].css',
chunkFilename: 'app/css/[name].[contenthash:8].css',
ignoreOrder: true
}),
// 必须和MiniCssExtractPlugin配合,删除没用的css
new PurgecssPlugin({
paths: glob.sync(`${config.appSrc}/**/*`, { nodir: true })
}),
// 压缩css
new OptimizeCssAssetsPlugin({
cssProcessor: require('cssnano'),
cssProcessorPluginOptions: {
preset: ['default', { discardComments: { removeAll: true } }]
},
canPrint: true //是否将插件信息打印到控制台
}),
复制代码
new TerserPlugin({
cache: true,
extractComments: false,
parallel: true, //并行压缩
terserOptions: {
ecma: undefined,
warnings: false,
parse: {},
compress: {},
mangle: false, // Note `mangle.properties` is `false` by default.
module: false,
output: null,
toplevel: false,
nameCache: null,
ie8: false,
keep_classnames: undefined,
keep_fnames: false,
safari10: false
}
}),
复制代码
new HardSourceWebpackPlugin({
configHash: function(webpackConfig) {
return require('node-object-hash')({ sort: false }).hash(
webpackConfig
);
},
info: {
mode: 'none',
level: 'debug'
},
cachePrune: {
maxAge: 2 * 24 * 60 * 60 * 1000,
sizeThreshold: 50 * 1024 * 1024
},
environmentHash: {
root: process.cwd(),
directories: [],
files: ['package-lock.json', 'yarn.lock']
}
})
复制代码
最后看下咱们用到的全部的插件以及配置
{
"name": "",
"version": "1.0.0",
"description": "",
"main": "index.js",
"homepage": "",
"husky": {
"hooks": {
"commit-msg": "commitlint -E HUSKY_GIT_PARAMS",
"pre-commit": "lint-staged"
}
},
"lint-staged": {
"src/**/*.{jsx,txs,ts,js,json,md}": [
"prettier --write",
"eslint --fix",
"git add"
]
},
"scripts": {
"fix": "eslint --ext .js,.ts src --fix",
"test": "jest --config jestconfig.js --coverage",
"start": "cross-env NODE_ENV=development webpack-dev-server --config config/webpack.config.dev.js --inline --color --progress --hot",
"build": "cross-env NODE_ENV=production webpack --config config/webpack.config.prod.js --progress --color",
"mock": "cross-env NODE_ENV=development IS_Mock=true webpack-dev-server --config config/webpack.config.dev.js --inline --color --progress --hot"
},
"author": "",
"dependencies": {
"antd": "^4.0.2",
"axios": "^0.19.2",
"classnames": "^2.2.6",
"echarts": "^4.6.0",
"echarts-for-react": "^2.0.15-beta.1",
"echarts-liquidfill": "^2.0.5",
"echarts-stat": "^1.1.1",
"echarts-wordcloud": "^1.1.3",
"history": "^4.10.1",
"lottie-web": "^5.6.6",
"md5": "^2.2.1",
"mockjs": "^1.1.0",
"prop-types": "^15.7.2",
"react": "^16.13.0",
"react-dom": "^16.13.0",
"react-redux": "^7.2.0",
"react-router": "^5.1.2",
"react-router-breadcrumbs-hoc": "^3.2.5",
"react-router-dom": "^5.1.2",
"redux": "^4.0.5",
"redux-thunk": "^2.3.0"
},
"devDependencies": {
"@babel/cli": "^7.8.4",
"@babel/core": "^7.8.7",
"@babel/plugin-proposal-class-properties": "^7.8.3",
"@babel/plugin-proposal-decorators": "^7.8.3",
"@babel/plugin-proposal-do-expressions": "^7.8.3",
"@babel/plugin-proposal-export-default-from": "^7.8.3",
"@babel/plugin-proposal-export-namespace-from": "^7.8.3",
"@babel/plugin-proposal-function-bind": "^7.8.3",
"@babel/plugin-proposal-function-sent": "^7.8.3",
"@babel/plugin-proposal-json-strings": "^7.8.3",
"@babel/plugin-proposal-logical-assignment-operators": "^7.8.3",
"@babel/plugin-proposal-nullish-coalescing-operator": "^7.8.3",
"@babel/plugin-proposal-numeric-separator": "^7.8.3",
"@babel/plugin-proposal-optional-chaining": "^7.8.3",
"@babel/plugin-proposal-pipeline-operator": "^7.8.3",
"@babel/plugin-proposal-throw-expressions": "^7.8.3",
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
"@babel/plugin-syntax-import-meta": "^7.8.3",
"@babel/plugin-transform-arrow-functions": "^7.8.3",
"@babel/plugin-transform-modules-commonjs": "^7.8.3",
"@babel/plugin-transform-runtime": "^7.8.3",
"@babel/polyfill": "^7.8.7",
"@babel/preset-env": "^7.8.7",
"@babel/preset-react": "^7.8.3",
"@babel/preset-stage-0": "^7.8.3",
"@babel/runtime-corejs3": "^7.8.7",
"@commitlint/cli": "^8.3.5",
"@commitlint/config-conventional": "^8.3.4",
"@hot-loader/react-dom": "^16.12.0",
"@svgr/webpack": "^5.2.0",
"antd-dayjs-webpack-plugin": "^0.0.8",
"autoprefixer": "^9.7.4",
"babel-eslint": "^10.1.0",
"babel-jest": "^25.1.0",
"babel-loader": "^8.0.6",
"babel-plugin-import": "^1.13.0",
"bundle-loader": "^0.5.6",
"chalk": "^3.0.0",
"clean-webpack-plugin": "^3.0.0",
"compression-webpack-plugin": "^3.1.0",
"copy-webpack-plugin": "^5.1.1",
"core-js": "^3.6.4",
"cross-env": "^7.0.2",
"css-loader": "^3.4.2",
"cssnano": "^4.1.10",
"cz-conventional-changelog": "^3.1.0",
"enzyme": "^3.11.0",
"enzyme-adapter-react-16": "^1.15.2",
"eslint": "^6.8.0",
"eslint-config-airbnb-base": "^14.1.0",
"eslint-config-prettier": "^6.10.0",
"eslint-friendly-formatter": "^4.0.1",
"eslint-loader": "^3.0.3",
"eslint-plugin-jsdoc": "^22.0.0",
"eslint-plugin-prettier": "^3.1.2",
"eslint-plugin-react": "^7.16.0",
"eslint-plugin-react-hooks": "^2.2.0",
"file-loader": "^5.1.0",
"hard-source-webpack-plugin": "^0.13.1",
"hash-sum": "^2.0.0",
"html-webpack-plugin": "^3.2.0",
"html-webpack-tags-plugin": "^2.0.17",
"husky": "^4.2.3",
"identity-obj-proxy": "^3.0.0",
"image-webpack-loader": "^6.0.0",
"jest": "^25.1.0",
"less": "^3.11.1",
"less-loader": "^5.0.0",
"lint-staged": "^10.0.8",
"mini-css-extract-plugin": "^0.9.0",
"optimize-css-assets-webpack-plugin": "^5.0.3",
"os": "^0.1.1",
"portfinder": "^1.0.25",
"postcss-loader": "^3.0.0",
"postcss-preset-env": "^6.7.0",
"postcss-px-to-viewport": "^1.1.1",
"prettier": "^1.19.1",
"progress-bar-webpack-plugin": "^2.1.0",
"purgecss-webpack-plugin": "^2.1.0",
"react-hot-loader": "4.12.19",
"sass": "^1.26.2",
"sass-loader": "^8.0.2",
"sass-resources-loader": "^2.0.1",
"speed-measure-webpack-plugin": "^1.3.1",
"style-loader": "^1.1.3",
"svg-url-loader": "^4.0.0",
"terser-webpack-plugin": "^2.3.5",
"thread-loader": "^2.1.3",
"url-loader": "^3.0.0",
"webpack": "^4.42.0",
"webpack-bundle-analyzer": "^3.6.1",
"webpack-cli": "^3.3.11",
"webpack-dev-server": "^3.10.3",
"webpack-merge": "^4.2.2",
"webpackbar": "^4.0.0"
},
"license": "ISC",
"config": {
"commitizen": {
"path": "./node_modules/cz-conventional-changelog"
}
}
}
复制代码
scripts:配置了开发和打包的方法,cross-env提供了跨平台的使用环境变量的做用。
__ tests __包含这测试文件
jestconfig是测试文件的一些基本配置
module.exports = {
setupFiles: ['<rootDir>/src/__tests__/setup.js'],
moduleFileExtensions: ['js', 'json', 'jsx', 'ts', 'tsx', 'node'],
testPathIgnorePatterns: ['/node_modules/'],
testRegex: '.*\\.test\\.js$',
collectCoverage: false,
moduleNameMapper: {
'\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga|css|less|scss)$':
'identity-obj-proxy',
'^@pages(.*)$': '<rootDir>/src/pages$1'
},
transform: {
'^.+\\.js$': 'babel-jest'
}
};
复制代码
可使用npm run test进行测试
"config": {
"commitizen": {
"path": "./node_modules/cz-conventional-changelog"
}
},
"husky": {
"hooks": {
"commit-msg": "commitlint -E HUSKY_GIT_PARAMS",
"pre-commit": "lint-staged"
}
},
"lint-staged": {
"src/**/*.{jsx,txs,ts,js,json,md}": [
"prettier --write",
"eslint --fix",
"git add"
]
},
复制代码
为了规范代码格式,以及提交代码规范,咱们可使用一些工具
提交审查工具:
全局安装commitizen/cz-cli,是一个能够实现规范的提交说明的工具
commitizen init cz-conventional-changelog --save --save-exact 建立一个angula规范的提交工具
"config": {
"commitizen": {
"path": "./node_modules/cz-conventional-changelog"
}
},
复制代码
这段代码会自动生成
commitlint/cli提交校验工具
commitlint/config-conventional 安装符合Angular风格的校验规则
加入husky