項目基本結構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