开发环境构建
1. init项目
mkdir react-family && cd react-family
npm init
2. webpack
npm install --save-dev webpack@3 -g //webpack已有4、5, webpack需要全局安装
//--save-dev就是指开发时的依赖,--save指发布时还依赖的东西
//创建webpack.dev.config.js,在里面编写配置webpack项
webpack --config webpack.dev.config.js //运行webpack打包
//可以在package.json的script中加入对应的脚本命令行,简化运行
script: {
"dev-build": "webpack --config webpack.dev.config.js"
}
3. babel(babel-core, babel-loader, babel-preset-es2015, babel-preset-stage-0, babel-preset, react)
//babel-core 调用Babel的API进行转码
//babel-loader
//babel-preset-es2015 用于解析ES6
//babel-preset-stage-0 用于解析ES7提案
npm install --save-dev babel-core babel-loader babel-preset-es2015 babel-preset-stage-0 babel-preset-react
//新建 .babelrc配置文件配置babel
{
"presets": ["es2015", "stage-0", "react"],
"plugins":[]
}
/*src文件夹下面的以.js结尾的文件,要使用babel解析*/
/*cacheDirectory是用来缓存编译结果,下次编译加速*/
module: {
rules: [{
test: /\.js$/,
use: ['babel-loader?cacheDirectory=true'],
include: path.join(__dirname, 'src')
}]
}
4. react(react, react-dom)
npm install --save react react-dom
5. react-router(react-router-dom)
npm install --save react-router-dom
// Router - Routes - Route Link
// element
import React from "react";
import { BrowserRouter as Router, Route, Routes, Link } from "react-router-dom";
import Bundle from "./bundle";
import Home from "bundle-loader?lazy&name=home!pages/Home/Home";
import Page1 from "bundle-loader?lazy&name=page1!pages/Page1/Page1";
import Counter from "bundle-loader?lazy&name=counter!../pages/Counter";
import UserInfo from "bundle-loader?lazy&name=userInfo!../pages/UserInfo";
const Loading = () => <div>Loading........</div>;
const createComponent = (component) => (props) =>
(
<Bundle load={component}>
{(Component) => (Component ? <Component {...props} /> : <Loading />)}
</Bundle>
);
const getRouter = () => {
const HomePage = createComponent(Home);
const Page1Page = createComponent(Page1);
const CounterPage = createComponent(Counter);
const UserInfoPage = createComponent(UserInfo);
return (
<Router>
<div>
<ul>
<li>
<Link to="/">Home do you trust me?</Link>
</li>
<li>
<Link to="/page1">Page1 are always good!</Link>
</li>
<li>
<Link to="/counter">get yourself a counter!</Link>
</li>
<li>
<Link to="/userInfo">something about me !</Link>
</li>
</ul>
<Routes>
<Route exact path="/" element={<HomePage />} />
<Route path="/page1" element={<Page1Page />} />
<Route path="/counter" element={<CounterPage />} />
<Route path="/userInfo" element={<UserInfoPage />} />
</Routes>
</div>
</Router>
);
};
export default getRouter;
此时点击链接不会按照预期正确的路由跳转。
因为之前的一直访问的是静态文件路径,类似与 file://react/react-family/dist/index.html。
这种路径不是我们想象中的的路由那样的路径http://localhost:3000 。
此时开发环境需要配置一个简单的web服务器,来伺服静态文件。(使用webpack-dev-server,或者nextjs搭配koa等框夹启动一个本地服务器,使用Nginx、Apache、IIS等配置来启动一个简单的web服务器)
6. web-dev-server
npm install --save webpack-dev-server@2 -g //需全局安装
// webpack.dev.config.js
devServer: {
contentBase: path.join(__dirname, './dist') // Url的根目录。如果不指定,默认指向项目根目录
}
// 丰富devServer配置选项
devServer: {
contentBase: path.join(__dirname, 'dist'),
port: 8080,
historyApiFallback: true, // 然所有404定位到index.html
host: '0.0.0.0' //指定一个host,默认localhost。'0.0.0.0'代表希望外部外部服务器可以访问到。
}
// 运行命令行及优化
webpack-dev-server --config webpack.dev.config.js
//package.json
script: {
"start": "webpack-dev-server --config webpack.dev.config.js --color --progress"
}
7. hmr(--hot,module.hot, react-hot-loader)
"start": "webpack-dev-server --config webpack.dev.config.js --color --progress --hot"
//index.js
if(module.hot) {
module.hot.accept();
}
// 文件别名
resolve: {}
8. Redux(redux,react-redux,redux-thunk)
npm install --save redux
npm install --save redux-thunk
9. 优化:Redux,报错提示
combineReducers
devtool:"inline-source-map"
- css-loader,style-loader
npm install css-loader style-loader --save-dev
// webpack.dev.config.js
{
test: /\.css$/,
use: ['style-loader', 'css-loader']
}
- url-loader,file-loader
npm install --save-dev url-loader file-loader
12. 按需加载(bundle-loader)
npm install bundle-loader --save-dev
13.缓存
output: {
path: path.join(__dirname, './dist'),
filename: '[name].[hash].js',
chunkFilename: '[name].[chunkhash].js'
}
14. HtmlWebpackPlugin(html-webpack-plugin)
npm install html-webpack-plugin --save-dev
15. 提取重复代码
// webpack.dev.config.js
entry: {
app: path.join(__dirname,'src/index.js'),
vendor:['react','react-dom','react-router-dom','redux', 'react-redux']
}
/*plugins*/
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor'
})
output: {
path: path.join(__dirname, './dist'),
filename: '[name].[hash].js', //这里应该用chunkhash替换hash
//无奈,如果用`chunkhash`,会报错。和`webpack-dev-server --hot`不兼容,具体[看这里](https://github.com/webpack/webpack-dev-server/issues/377)。
chunkFilename: '[name].[chunkhash].js'
}
生产环境构建
1. 剔除配置
-
web-dev-server -
--hot模块 -
改变: filename:[name].[thunkHash].js -
devtool:"cheap-module-source-map"
2. 文件压缩 uglifyjs-webpack-plugin
npm i --save-dev uglifyjs-webpack-plugin
3. 指定环境 webpack.DefinePlugin()
module.exports = {
plugins: [
new webpack.DefinePlugin({
'process.env': {
'NODE_ENV': JSON.stringify('production')
}
})
]
}
4. 优化打包 clean-webpack-plugin
npm install --save-dev clean-webpack-plugin
5. 优化缓存 webpack.HashedModuleIdsPlugin()
6. 抽取css extract-text-webpack-plugin
npm install --save-dev extract-text-webpack-plugin
// webpack.dev.config.js
module.exports = {
module: {
rules: [
{
test: /.css$/,
use: ExtractTextPlugin.extract({
fallback: "style-loader",
use: "css-loader"
})
}
]
},
plugins: [
new ExtractTextPlugin({
filename: '[name].[contenthash:5].css',
allChunks: true
})
]
}
7. 使用axios和middleware优化API请求
npm install --save axios
8. 合并提取webpack公共配置
npm install --save-dev webpack-merge
//webpck.common.config.js
//... webpack开发配置和生产配置的相同部分
//...
module.exports = commonConfig;
webpack.dev.config.js
const merge = require('webpack-merge');
const commonConfig = require('./webpack.common.config.js');
const devConfig = {
//... webpack开发 特有配置
//...
}
module.exports = merge({
customizeArray(a, b, key) {
/*entry.app不合并,全替换*/
if (key === 'entry.app') {
return b;
}
return undefined;
}
})(commonConfig, devConfig);
webpack.config.js
const merge = require('webpack-merge');
const commonConfig = require('./webpack.common.config.js');
const publicConfig = {
//... webpack生产 特有配置
//...
}
module.exports = merge(commonConfig, publicConfig);
9. 优化目录结构并增加404页面
10. 加入 babel-plugin-transform-runtime和babel-polyfill
//在转换 ES2015 语法为 ECMAScript 5 的语法时,babel 会需要一些辅助函数,例如 _extend。babel 默认会将这些辅助函数内联到每一个 js 文件里,这样文件多的时候,项目就会很大。
//所以 babel 提供了 transform-runtime 来将这些辅助函数“搬”到一个单独的模块 babel-runtime 中,这样做能减小项目文件的大小。
npm install --save-dev babel-plugin-transform-runtime
// .babelrc
// 修改`.babelrc`配置文件,增加配置
"plugins": ["transform-runtime"]
// Babel默认只转换新的JavaScript句法(syntax),而不转换新的API,比如Iterator、Generator、Set、Maps、Proxy、Reflect、Symbol、Promise等全局对象,以及一些定义在全局对象上的方法(比如Object.assign)都不会转码。
// 举例来说,ES6在Array对象上新增了Array.from方法。Babel就不会转码这个方法。如果想让这个方法运行,必须使用babel-polyfill,为当前环境提供一个垫片。
npm install --save-dev babel-polyfill
// webpack.common.config.js
app: [
"babel-polyfill",
path.join(__dirname, "src/index.js")
]
//webpack.dev.config.js
app:[
"babel-polyfill",
"rect-hot-loader/patch",
path.join(__dirname, "src/index.js")
]
11. 集成PostCSS
// postcss有很多插件 如: postcss-cssnext
// postcss-cssnext允许你使用未来的css特性
// postcss-cssnext包含autoprefixer
// autoprefixer可以自动给css属性加上浏览器前缀
npm install --save-dev postcss-loader
npm install --save-dev postcss-cssnext
// webpack.dev.config.js
rules:[{
test: /\.(css|scss)$/,
use: ["style-loader","css-loader","postcss-loader"]
}]
// webpack.config.js
rules:[{
test: /\.css$/,
use: ExtractTextPlugin.extract({
fallback: "style-loader",
use: ["css-loader", "postcss-loader"]
})
}]
//根目录添加postcss.config.js 配置文件
//postcss.config.js
module.export = {
plugins: {
"postcss-cssnext": {}
}
};
// 此后运行代码,Autoprefixer可以自动给css属性添加浏览器前缀
// 编译前
.container {
display: flex;
}
//编译后
.container {
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
}
12. redux模块热替换配置
// 当修改reducer 代码时,页面会整个刷新,而不是局部刷新
//增加一段监听reducers变化,并替换的代码
if(module.hot) {
module.hot.accept("./reducers", () => {
const nextCombineReducers = require("./reducers").default;
store.replaceReducer(nextCombineReducers);
})
}
13. 模拟AJAX数据之Mock.js
// Mock.js:拦截AJAX请求,返回需要的数据。
// 我们写ajax请求的时候,随便写,Mock.js会自动拦截。
// Mock.js提供各种随机生成数据。
npm install --save-dev mockjs
// 创建mock文件夹
// mock/mock.js
import Mock from "mockjs";
let Random = Mock.Random;
Mock.mock("/api/user",{ // 拦截/api/user请求
"name": "@cname", //返回一个随机的中文名字
"intro": "@word(20)", //返回一个20个字母的字符串
})
// 与项目连接
//在项目中import mock文件
// 在页面中正常请求,即可使用mock
import "../mock/mock";
promise: client => client.get('api/user');
//配置:只有在开发环境下,才引入mock,生产环境不引入。
// webpack.common.config.js
resolve: {
alias: {
...
mock: path.join(__dirname, 'mock')
}
}
// webpack.dev.config.js
const webpack = require('webpack');
plugins: [
new webpack.DefinePlugin({
MOCK: true
})
]
// src/index.js import mock的方式改变一下
if (MOCK) {
require("mock/mock");
}
14. 使用CSS Modules
// webpack.dev.config.js
module: {
rules: [{
test: /.css$/,
use: ["style-loader", "css-loader?modules&localIdentName=[local]-[hash:base64:5]", "postcss-loader"]
}]
}
//webpack.config.js
module: {
rules: [{
test: /.css$/,
use: ExtractTextPlugin.extract({
fallback: "style-loader",
use: ["css-loader?modules&localIdentName=[local]-[hash:base64:5]", "postcss-loader"]
})
}]
}
// src/pages/Page1/page1.css
.box {
border: 1px solid red;
}
// src/pages/Page1/Page1.js
import React, {Component} from 'react';
import style from './Page1.css';
import image from './images/brickpsert.jpg';
export default class Page1 extends Component {
render() {
return (
<div className={style.box}>
this is page1~
<img src={image}/>
</div>
)
}
}
15. 使用json-server代替Mock.js
json-server和Mock.js一样,都是用来模拟接口数据的。
json-server功能更强大,支持分页,排序,筛选等等,具体的可以去看文档。
我们用json-server代替之前的Mock.js。
-
删除
Mock.js相关代码。一共两处,
webpack.dev.config.js,src/index.js -
npm install --save-dev json-server -
写个demo,我们生成虚假数据还是用
mockjs。
mock/mock.js
let Mock = require('mockjs');
var Random = Mock.Random;
module.exports = function () {
var data = {};
data.user = {
'name': Random.cname(),
'intro': Random.word(20)
};
return data;
};
- 设置启动脚本
package.json
"mock": "json-server mock/mock.js --watch --port 8090",
"mockdev": "npm run mock & npm start"
- webpack.dev.config.js 增加个代理,把我们的API请求,代理到
json-server服务器去。
devServer: {
...
proxy: {
"/api/*": "http://localhost:8090/$1"
}
}
哦了,你可以npm run mockdev启动项目,然后访问我们之前的用户信息接口,试试啦。
问题:windows不支持命令并行执行&,你可以分开执行,或者使用npm-run-all