版本说明
此版本适用于任意 react 项目,并无特殊依赖。此版本主要用于为其他定制版本做铺垫,省去一些通用的配置解释。同时能够实现渐进式的应用,有利于同学们的学习。
版本功能
react、react-router、lodash、moment、less、css-modules、eslint、eslint-config-airbnb
安装命令
yarn add react react-dom react-router-dom lodash moment
yarn add -D cross-env \
webpack webpack-cli webpack-merge webpack-dev-server \
file-loader url-loader \
style-loader css-loader less less-loader postcss-loader autoprefixer \
babel-loader @babel/core @babel/preset-env @babel/runtime @babel/preset-react \
@babel/plugin-transform-runtime babel-plugin-lodash \
html-webpack-plugin mini-css-extract-plugin optimize-css-assets-webpack-plugin uglifyjs-webpack-plugin eslint-import-resolver-webpack
npx install-peerdeps -D eslint-config-airbnb
特别说明
cross-env
可以保证 windows 系统下的兼容babel-plugin-lodash
的安装,优化了 lodash 的打包体积IgnorePlugin
优化了 moment 的打包,去掉了所有的 locale,需要手动加载,参考这里- eslint 的安装比较特别,可参考这里
- IDE 需要安装 eslint 的扩展才能提示
eslint-import-resolver-webpack
可以解决 webpack.resolve.alias 的误报问题- postcss、eslint 的配置因为较简单,都写到了
package.json
中,并没有新建.eslintrc.js
和postcss.config.js
,详见下文
项目目录
webpack-demo
├── config
│ ├── webpack.base.js
│ ├── webpack.dev.js
│ └── webpack.prod.js
├── src
│ ├── apps
│ │ ├── Home
│ │ │ ├── index.jsx
│ │ │ └── style.less
│ │ ├── Lodash.jsx
│ │ └── Moment.jsx
│ └── index.jsx
├── .gitignore
├── index.html
├── package.json
├── webpack.config.js
└── yarn.lock
配置文件详情
package.json
{
"name": "webpack-demo",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"scripts": {
"dev": "cross-env NODE_ENV=development webpack-dev-server",
"build": "cross-env NODE_ENV=production webpack --progress --colors"
},
"dependencies": {
"lodash": "^4.17.11",
"moment": "^2.24.0",
"react": "^16.8.6",
"react-dom": "^16.8.6",
"react-router-dom": "^5.0.0"
},
"devDependencies": {
"@babel/core": "^7.4.4",
"@babel/plugin-transform-runtime": "^7.4.4",
"@babel/preset-env": "^7.4.4",
"@babel/preset-react": "^7.0.0",
"@babel/runtime": "^7.4.4",
"autoprefixer": "^9.5.1",
"babel-loader": "^8.0.5",
"babel-plugin-lodash": "^3.3.4",
"cross-env": "^5.2.0",
"css-loader": "^2.1.1",
"eslint": "5.3.0",
"eslint-config-airbnb": "17.1.0",
"eslint-import-resolver-webpack": "^0.11.1",
"eslint-plugin-import": "^2.14.0",
"eslint-plugin-jsx-a11y": "^6.1.1",
"eslint-plugin-react": "^7.11.0",
"file-loader": "^3.0.1",
"html-webpack-plugin": "^3.2.0",
"less": "^3.9.0",
"less-loader": "^5.0.0",
"mini-css-extract-plugin": "^0.6.0",
"optimize-css-assets-webpack-plugin": "^5.0.1",
"postcss-loader": "^3.0.0",
"style-loader": "^0.23.1",
"uglifyjs-webpack-plugin": "^2.1.2",
"url-loader": "^1.1.2",
"webpack": "^4.30.0",
"webpack-cli": "^3.3.2",
"webpack-dev-server": "^3.3.1",
"webpack-merge": "^4.2.1"
},
"postcss": {
"plugins": {
"autoprefixer": {}
}
},
"eslintConfig": {
"root": true,
"extends": [
"airbnb"
],
"env": {
"browser": true,
"node": true,
"es6": true,
"jest": true
},
"settings": {
"import/resolver": {
"webpack": {
"config": "./webpack.config.js"
}
}
}
}
}
webpack.config.js
let config;
switch (process.env.NODE_ENV) {
case "development":
config = require("./config/webpack.dev.js");
break;
case "production":
default:
config = require("./config/webpack.prod.js");
}
module.exports = config;
config/webpack.base.js
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const webpack = require('webpack');
const NODE_ENV = process.env.NODE_ENV || "development";
const PROJECT_PATH = path.resolve(__dirname, `../`);
module.exports = {
mode: NODE_ENV,
entry: {
index: path.resolve(PROJECT_PATH, "./src/index")
},
output: {
path: path.resolve(PROJECT_PATH, "./build"),
filename: `js/[name]${
NODE_ENV === "production" ? "_[contenthash:6]" : ""
}.js`,
libraryTarget: "umd"
},
module: {
rules: [
{
test: /\.m?jsx?$/,
include: [path.resolve(PROJECT_PATH, "src/")],
use: {
loader: "babel-loader",
options: {
presets: ["@babel/preset-env", "@babel/preset-react"],
plugins: ["@babel/plugin-transform-runtime", "lodash"]
}
}
},
/* copy from vue-cli start */
/* config.module.rule('images') */
{
test: /\.(png|jpe?g|gif|webp)(\?.*)?$/,
use: [
/* config.module.rule('images').use('url-loader') */
{
loader: "url-loader",
options: {
limit: 4096,
fallback: {
loader: "file-loader",
options: {
name: "img/[name]_[hash:8].[ext]"
}
}
}
}
]
},
/* config.module.rule('svg') */
{
test: /\.(svg)(\?.*)?$/,
use: [
/* config.module.rule('svg').use('file-loader') */
{
loader: "file-loader",
options: {
name: "img/[name]_[hash:8].[ext]"
}
}
]
},
/* config.module.rule('media') */
{
test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
use: [
/* config.module.rule('media').use('url-loader') */
{
loader: "url-loader",
options: {
limit: 4096,
fallback: {
loader: "file-loader",
options: {
name: "media/[name]_[hash:8].[ext]"
}
}
}
}
]
},
/* config.module.rule('fonts') */
{
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/i,
use: [
/* config.module.rule('fonts').use('url-loader') */
{
loader: "url-loader",
options: {
limit: 4096,
fallback: {
loader: "file-loader",
options: {
name: "fonts/[name]_[hash:8].[ext]"
}
}
}
}
]
}
/* copy from vue-cli end */
]
},
resolve: {
extensions: [".mjs", ".js", ".jsx", ".json", ".ts", ".tsx"],
alias: {
"@": path.resolve(PROJECT_PATH, "./src/")
}
},
plugins: [
new HtmlWebpackPlugin({
template: path.resolve(PROJECT_PATH, "./index.html")
}),
new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/)
],
optimization: {
chunkIds: "named",
moduleIds: "hashed",
splitChunks: {
chunks: "initial",
name: "vendors"
},
runtimeChunk: {
name: entrypoint => `runtime_${entrypoint.name}`
}
}
};
config/webpack.dev.js
const webpackMerge = require("webpack-merge");
const baseConfig = require("./webpack.base");
const config = {
module: {
rules: [
{
test: /\.(less|css)$/,
use: [
"style-loader",
{
loader: "css-loader",
options: {
importLoaders: 2,
modules: true,
localIdentName: "[folder]-[local]-[hash:base64:5]"
}
},
"postcss-loader",
"less-loader"
]
}
]
},
devtool: "cheap-module-eval-source-map"
};
module.exports = webpackMerge(config, baseConfig);
config/webpack.prod.js
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const webpackMerge = require("webpack-merge");
const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin");
const UglifyJsPlugin = require("uglifyjs-webpack-plugin");
const baseConfig = require("./webpack.base");
const config = {
module: {
rules: [
{
test: /\.(less|css)$/,
use: [
MiniCssExtractPlugin.loader,
{
loader: "css-loader",
options: {
importLoaders: 2,
modules: true,
localIdentName: "[folder]-[local]-[hash:base64:5]"
}
},
"postcss-loader",
"less-loader"
]
}
]
},
plugins: [
new MiniCssExtractPlugin({
filename: "css/[name]_[contenthash:6].css",
allChunks: true
})
],
optimization: {
minimizer: [
new UglifyJsPlugin({
cache: true,
parallel: true,
sourceMap: false // set to true if you want JS source maps
}),
new OptimizeCSSAssetsPlugin({})
]
}
};
module.exports = webpackMerge(config, baseConfig);
.gitignore
# copy from vue-cli and create-react-app
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
/tests/e2e/reports/
selenium-debug.log
# production
/build
/dist
# misc
.DS_Store
.env.local
.env.*.local
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
Demo 文件详情 TL;DR
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Demo</title>
</head>
<body>
<div id="root"></div>
</body>
</html>
src/index.jsx
import React from "react";
import ReactDOM from "react-dom";
import { BrowserRouter as Router, Route, Link } from "react-router-dom";
import Home from "@/apps/Home";
import Lodash from "@/apps/Lodash";
import Moment from "@/apps/Moment";
ReactDOM.render(
<Router>
<nav>
<ul>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="/lodash/">Lodash</Link>
</li>
<li>
<Link to="/moment/">Moment</Link>
</li>
</ul>
</nav>
<Route path="/" exact component={Home} />
<Route path="/lodash/" component={Lodash} />
<Route path="/moment/" component={Moment} />
</Router>,
document.getElementById("root")
);
src/apps/Lodash.jsx
import React from "react";
import _ from "lodash";
const Lodash = props => (
<div>
<h1>Lodash</h1>
<p>{`The result of \`_.camelCase('camel-case')\` is ${_.camelCase(
"camel-case"
)}`}</p>
</div>
);
export default Lodash;
src/apps/Moment.jsx
import React from "react";
import moment from "moment";
const Moment = props => (
<div>
<h1>Moment</h1>
<p>{moment().format()}</p>
</div>
);
export default Moment;
src/apps/Home/index.jsx
import React from "react";
import style from "./style.less";
const Home = props => (
<div className={style.home}>
<h1>Home</h1>
<p>This is Home page which uses CSS-Modules</p>
</div>
);
export default Home;
src/apps/Home/style.less
.home {
text-align: center;
color: red;
}