📦 ariesly15 / webpack-antd-demo

5 stars 2 forks 👁 5 watching
📥 Clone https://github.com/ariesly15/webpack-antd-demo.git
HTTPS git clone https://github.com/ariesly15/webpack-antd-demo.git
SSH git clone git@github.com:ariesly15/webpack-antd-demo.git
CLI gh repo clone ariesly15/webpack-antd-demo
李燚 李燚 更新目录结构 b11b95f 7 years ago 📝 History
📂 master View all commits →
📁 src
📄 .babelrc
📄 .gitignore
📄 README.md
📄 README.md

React框架入门

说明

项目托管于GitHub, 文档很多, 需要有耐心

1.本教程基于mac环境

`` sh node -v # v6.9.5 npm -v # 3.10.10 %%CODEBLOCK0%% sh cd src/pages mkdir Home %%CODEBLOCK1%% webpack-antd-demo ├── antd@3.8.4 ├── react@16.4.2 ├── react-dom@16.4.2 └── webpack@4.17.1 %%CODEBLOCK2%% webpack-antd-demo ├── README.md // 本教程 ├── package.json ├── pages // 放置页面, 业务页面代码 ├── src │   ├── index.html // 模板, HtmlWebpackPlugin插件会把相关资源注入后放入dist文件夹 │   ├── index.js // 项目入口 │   ├── app.js // 页面入口 │   ├── api // 请求的api │   ├── assets // 资源文件夹 │   ├── bootstrap // 项目入口之前执行 │   │   ├── http-interceptors.js // 网络请求拦截器 │   │   └── index.js // bootstrap入口文件 │   ├── common │   │   ├── constants.js // 用于存放静态变量 │   │   └── utils.js // 放置公共方法 │   ├── component // 自定义组件 , 例如 Loading 和 404 │   │   ├── Loading │   │   │   └── index.js │   │   └── NotFound │   │   └── index.js │   ├── I18N // 国际化 │   ├── pages // 业务页面代码 │   ├── router // 路由 │   │   └── index.js │   └── store // 数据管理 │   ├── app.js │   ├── index.js // 入口, 根据业务自行创建 │   └── ui.js ├── webpack.common.js // webpack 公共配置 ├── webpack.dev.js // webpack 开发配置 └── webpack.prod.js // webpack 线上配置 %%CODEBLOCK3%% sh mkdir webpack-antd-demo && cd webpack-antd-demo npm init # 按照提示填写基本信息或者用 npm init -y 一步创建完成 %%CODEBLOCK4%% sh npm i webpack webpack-cli --save-dev # --save 线上依赖 # --save-dev 开发依赖 %%CODEBLOCK5%% sh touch webpack.dev.js %%CODEBLOCK6%% js // webpack.dev.js 内容 const path = require('path'); module.exports = { /*入口 (webpacak@4可以不写, 默认读取./src/index.js)*/ entry: path.join(__dirname, 'src/index.js'), /*输出到dist文件夹,输出文件名字为bundle.js*/ output: { path: path.join(__dirname, './dist'), filename: 'bundle.js' } } %%CODEBLOCK7%% sh mkdir src && touch ./src/index.js %%CODEBLOCK8%% js // ./src/index.js 内容 document.getElementById('app').innerHTML = "webpack-antd-demo" %%CODEBLOCK9%% json // 在package.json中的scrripts下添加 "dev": "webpack --config webpack.dev.js --mode development" // 结果入下 { "name": "webpack-antd-demo", "version": "1.0.0", "description": "## 说明", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", "dev": "webpack --config webpack.dev.js --mode development" }, "keywords": [], "author": "", "license": "ISC", "devDependencies": { "webpack": "^4.17.1", "webpack-cli": "^3.1.0" } } %%CODEBLOCK10%% sh npm run dev %%CODEBLOCK11%% sh # 在dist目录创建html文件 touch ./dist/index.html %%CODEBLOCK12%% html <!-- dist/index.html内容 --> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>webpack-ant-demo</title> </head> <body> <div id="app"></div> <script type="text/javascript" src="./bundle.js"></script> </body> </html> %%CODEBLOCK13%% sh # 安装 npm i --save-dev babel-loader@8 @babel/core @babel/preset-env %%CODEBLOCK14%% sh touch .babelrc %%CODEBLOCK15%% json // .babelrc 内容 { "presets": ["@babel/preset-env"], "plugins": [] } %%CODEBLOCK16%% js // 修改webpack.dev.js,增加babel-loader! module: { rules: [{ test: /\.js$/, // 正则匹配以 .js 结尾的文件来使用 babel 解析 use: ['babel-loader?cacheDirectory=true'], // cacheDirectory是用来缓存编译结果,下次编译加速 include: path.join(__dirname, 'src') // 需要解析的目录 }] } %%CODEBLOCK17%% js // 修改 ./src/index.js 使用es6的箭头函数 const useBabel = text => document.getElementById('app').innerHTML = text useBabel('正在使用babel') %%CODEBLOCK18%% sh npm i --save react react-dom %%CODEBLOCK19%% js import React from 'react' import {render} from 'react-dom' render( <div>Hello React !!!</div>, document.getElementById('app') ) %%CODEBLOCK20%% sh npm i @babel/preset-react --save-dev %%CODEBLOCK21%% js // 修改后的内容 { "presets": ["@babel/preset-env", "@babel/preset-react"], "plugins": [] } %%CODEBLOCK22%% sh cd src mkdir component cd component mkdir Hello cd Hello touch index.js %%CODEBLOCK23%% jsx import React, {Component} from 'react'; export default class Hello extends Component { render() { return <div> 我是独立的 Hello 组件 </div> } } %%CODEBLOCK24%% jsx // 修改 ./src/index.js 为如下内容 import React from 'react' import {render} from 'react-dom' import Hello from './component/Hello' render( <Hello/>, document.getElementById('app') ) %%CODEBLOCK25%% sh # 安装antd npm i antd --save %%CODEBLOCK26%% sh npm i babel-plugin-import --save-dev %%CODEBLOCK27%% js // 修改 .babelrc 文件, 修改后的文件内容如下 { "presets": ["@babel/preset-env", "@babel/preset-react"], "plugins": [ ["import",{ "libraryName": "antd", "style": "css" }] ] } %%CODEBLOCK28%% js // babel-plugin-import 会帮助你加载 JS 和 CSS import { Alert } from 'antd'; %%CODEBLOCK29%% sh npm i css-loader style-loader --save-dev %%CODEBLOCK30%% js // 修改 webpack.dev.js, 修改后内容如下 const path = require("path"); module.exports = { entry: path.join(__dirname, "src/index.js"), output: { path: path.join(__dirname, "./dist"), filename: "bundle.js" }, module: { rules: [ { test: /\.js$/, // 正则匹配以 .js 结尾的文件来使用 babel 解析 use: ["babel-loader?cacheDirectory=true"], // cacheDirectory是用来缓存编译结果,下次编译加速 include: path.join(__dirname, "src") // 需要解析的目录 }, { test: /\.css$/, use: ['style-loader', 'css-loader'] } ] } }; %%CODEBLOCK31%% js // 修改后的 Hello/index.js 内容如下 import React, {Component} from 'react'; import { Alert } from "antd"; export default class Hello extends Component { render() { return <div> 我是独立的 Hello 组件 <Alert message="Success Text" type="success" /> <Alert message="Info Text" type="info" /> <Alert message="Warning Text" type="warning" /> <Alert message="Error Text" type="error" /> </div> } } %%CODEBLOCK32%% sh npm i html-webpack-plugin --save-dev %%CODEBLOCK33%% sh cd src && touch index.html %%CODEBLOCK34%% html <!-- ./src/index.html 内容如下 --> <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>webpack-antd-demo</title> </head> <body> <div id="app"></div> </body> </html> %%CODEBLOCK35%% js const path = require("path"); const HtmlWebpackPlugin = require('html-webpack-plugin'); module.exports = { output: { path: path.join(__dirname, "./dist"), filename: "bundle.js" }, module: { rules: [ { test: /\.js$/, // 正则匹配以 .js 结尾的文件来使用 babel 解析 use: ["babel-loader?cacheDirectory=true"], // cacheDirectory是用来缓存编译结果,下次编译加速 include: path.join(__dirname, "src") // 需要解析的目录 }, { test: /\.css$/, use: ['style-loader', 'css-loader'] } ] }, plugins: [ new HtmlWebpackPlugin({ filename: 'index.html', template: path.join(__dirname, 'src/index.html') }) ] }; %%CODEBLOCK36%% sh # 安装 npm i --save react-router-dom # 新建router文件夹和组件 cd src mkdir router && touch router/index.js %%CODEBLOCK37%% js // TestAntd.js import React, {Component} from 'react'; import { Alert } from "antd"; export default class TestAntd extends Component { render() { return <div> <Alert message="我是独立的 TestAntd 组件" type="success" /> </div> } } // TestRouter.js import React, {Component} from 'react'; import { Alert } from "antd"; export default class TestRouter extends Component { render() { return <div> <Alert message="我是独立的 TestRouter 组件" type="info" /> </div> } } %%CODEBLOCK38%% js // router/index.js 内容 import React from 'react' import {BrowserRouter as Router, Route, Switch, Link} from 'react-router-dom' import Hello from '../component/Hello' import TestAntd from '../component/Hello/TestAntd' import TestRouter from '../component/Hello/TestRouter' const getRouter = () => <Router> <div> <ul> <li><Link to="/">首页</Link></li> <li><Link to="/router">TBestRouter</Link></li> <li><Link to="/antd">TestAntd</Link></li> </ul> <Switch> <Route exact path="/" component={Hello} /> <Route exact path="/antd" component={TestAntd}/> <Route exact path="/router" component={TestRouter}/> </Switch> </div> </Router> export default getRouter %%CODEBLOCK39%% js // 把 import {BrowserRouter as Router, Route, Switch, Link} from 'react-router-dom' // 修改为 import {HashRouter as Router, Route, Switch, Link} from 'react-router-dom' %%CODEBLOCK40%% sh # 安装 npm i webpack-dev-server --save-dev %%CODEBLOCK41%% js module.exports = { //... devServer: { contentBase: path.join(__dirname, 'dist'), port: 9090 } } %%CODEBLOCK42%% js { // ... "scripts": { // ... "server": "webpack-dev-server --config webpack.dev.js --mode development" }, // ... } %%CODEBLOCK43%% sh npm i --save-dev url-loader file-loader %%CODEBLOCK44%% js // 修改webpack.dev.js, 添加规则(放在rules下) module.exports = { // ... module: { rules: [ // ... { test: /\.(png|jpg|gif)$/, use: [{ loader: 'url-loader', options: { // olimit 8192意思是,小于等于8K的图片会被转成base64编码,直接插入HTML中,减少HTTP请求 limit: 8192 } }] } ] }, // ... } %%CODEBLOCK45%% sh # 创建图片目录 mkdir -p src/assets/images %%CODEBLOCK46%% js // 修改 Hello/index.js 文件添加如下代码(图片文件在源码中获取) <div> <div>6kb</div> <img src={require('../../assets/images/antd6kb.png')} /> <div>10kb</div> <img src={require('../../assets/images/antd10kb.png')} /> </div> %%CODEBLOCK47%% sh # 安装 react-loadable npm i react-loadable --save # 安装 babel-plugin-syntax-dynamic-import 来支持react-loadable的import方法 npm i babel-plugin-syntax-dynamic-import --save-dev %%CODEBLOCK48%% js { // ... "plugins": [ // ... "syntax-dynamic-import" ] } %%CODEBLOCK49%% js // Loading 组件路径 src/component/Loading/index.js import React, { Component } from "react"; export default class Loading extends Component { render() { const { isLoading, error } = this.props; // Handle the loading state if (isLoading) { return <div>Loading...</div> } // Handle the error state else if (error) { return <div> Sorry, there was a problem loading the page. <div>{JSON.stringify(error, null, 4)}</div> </div> } else { return null } } } %%CODEBLOCK50%% js // ... import Loadable from 'react-loadable' import Loading from '../component/Loading' // ... /** 此处为删除项 import TestAntd from '../component/Hello/TestAntd' import TestRouter from '../component/Hello/TestRouter' */ const TestAntd = Loadable({ loader: () => import('../component/Hello/TestAntd'), loading: Loading }) const TestRouter = Loadable({ loader: () => import('../component/Hello/TestRouter'), loading: Loading }) %%CODEBLOCK51%% sh npm i @babel/plugin-proposal-class-properties --save-dev %%CODEBLOCK52%% js { // ... "plugins": [ // ... "@babel/plugin-proposal-class-properties" ] } %%CODEBLOCK53%% js import React, {Component} from 'react'; import { Alert } from "antd"; export default class TestAntd extends Component { static defaultProps = { value: 'test static' } render() { const {value} = this.props return <div> <Alert message="我是独立的 TestAntd 组件" type="success" /> <div>{value}</div> </div> } } %%CODEBLOCK54%% js module.exports = { // ... optimization: { runtimeChunk: 'single' } } %%CODEBLOCK55%% js module.exports = { output: { // ... filename: "[name].[chunkhash].js" }, // ... plugins: [ // ... new webpack.HashedModuleIdsPlugin() ], // ... optimization: { runtimeChunk: "single", splitChunks: { cacheGroups: { vendor: { test: /[\\/]node_modules[\\/]/, name: "vendors", chunks: "all" } } } } } %%CODEBLOCK56%% sh touch webpack.prd.js %%CODEBLOCK57%% js // webpack.prod.js const path = require("path"); const webpack = require("webpack"); const HtmlWebpackPlugin = require("html-webpack-plugin"); module.exports = { mode: 'production', output: { path: path.join(__dirname, "./dist"), filename: "[name].[chunkhash].js" }, module: { rules: [ { test: /\.js$/, // 正则匹配以 .js 结尾的文件来使用 babel 解析 use: ["babel-loader?cacheDirectory=true"], // cacheDirectory是用来缓存编译结果,下次编译加速 include: path.join(__dirname, "src") // 需要解析的目录 }, { test: /\.css$/, use: ["style-loader", "css-loader"] }, { test: /\.(png|jpg|gif)$/, use: [ { loader: "url-loader", options: { // olimit 8192意思是,小于等于8K的图片会被转成base64编码,直接插入HTML中,减少HTTP请求 limit: 8192 } } ] } ] }, plugins: [ new HtmlWebpackPlugin({ filename: "index.html", template: path.join(__dirname, "src/index.html") }), new webpack.HashedModuleIdsPlugin() ], optimization: { runtimeChunk: "single", splitChunks: { cacheGroups: { vendor: { test: /[\\/]node_modules[\\/]/, name: "vendors", chunks: "all" } } } } }; %%CODEBLOCK58%% js // 修改package.json, 添加scripts { // ... "scripts": { // ... "prod": "webpack --config webpack.prod.js --color --progress" }, // ... } %%CODEBLOCK59%% sh # 创建公共配置 touch webpack.common.js # 安装 webpack-merge npm i webpack-merge --save-dev %%CODEBLOCK60%% js // 修改 package.json文件, 修改后的 scripts 如下 { // ... "scripts": { "dev": "webpack-dev-server --config webpack.dev.js", // 由于目前webpack.prod.js和webpack.common.js只是相差了一个 mode, 所以修改为如下命令, 后续添加less是统一抽离 "prod": "webpack --config webpack.common.js --mode production --color --progress" }, // ... } %%CODEBLOCK61%% js // 抽取公共配置到 webpack.common.js 中 const path = require("path"); const webpack = require("webpack"); const HtmlWebpackPlugin = require("html-webpack-plugin"); module.exports = { output: { path: path.join(__dirname, "./dist"), filename: "[name].[chunkhash].js" }, module: { rules: [ { test: /\.js$/, use: ["babel-loader?cacheDirectory=true"], include: path.join(__dirname, "src") }, { test: /\.css$/, use: ["style-loader", "css-loader"] }, { test: /\.(png|jpg|gif)$/, use: [ { loader: "url-loader", options: { limit: 8192 } } ] } ] }, plugins: [ new HtmlWebpackPlugin({ filename: "index.html", template: path.join(__dirname, "src/index.html") }), new webpack.HashedModuleIdsPlugin() ], optimization: { runtimeChunk: "single", splitChunks: { cacheGroups: { vendor: { test: /[\\/]node_modules[\\/]/, name: "vendors", chunks: "all" } } } } }; // webpack.dev.js 修改如下 const path = require("path"); const CommonConfig = require("./webpack.common"); const WebpackMerge = require("webpack-merge"); const DevConfig = { devtool: "inline-source-map", mode: "development", devServer: { contentBase: path.join(__dirname, "dist"), historyApiFallback: true, // 指定使用一个 host。默认是 localhost。如果你希望服务器外部可访问,写法如下 host: "0.0.0.0", port: 9090 } }; module.exports = WebpackMerge(CommonConfig, DevConfig); %%CODEBLOCK62%% sh npm i clean-webpack-plugin --save-dev %%CODEBLOCK63%% js // webpack.common.js const CleanWebpackPlugin = require('clean-webpack-plugin'); plugins: [ new CleanWebpackPlugin(['dist']) ] %%CODEBLOCK64%% sh # 安装 npm i mobx mobx-react --save # 使用mobx开发, 需要启用decorators装饰器 npm i @babel/plugin-proposal-decorators --save-dev # 创建 store 目录 cd src && mkdir store && cd store touch index.js app.js %%CODEBLOCK65%% js // 修改 .balelrc 文件 { "presets": ["@babel/preset-env", "@babel/preset-react"], "plugins": [ ["@babel/plugin-proposal-decorators", {"legacy": true}], ["import",{ "libraryName": "antd", "style": "css" }], "syntax-dynamic-import", ["@babel/plugin-proposal-class-properties", {"loose" : true}] ] } %%CODEBLOCK66%% jsx // store/index.js import AppStore from './app' export const appStore = new AppStore() // store/app.js import {observable, action} from 'mobx' export default class App { @observable count = 0 @action updateCount(num){ this.count = this.count + num } } // src/index.js import React from 'react' import {render} from 'react-dom' import getRouter from './router' import * as stores from './store' import {Provider} from 'mobx-react' render(<Provider {...stores}> {getRouter()} </Provider>,document.getElementById('app')) %%CODEBLOCK67%% jsx // TestAntd/index.js import React, {Component} from 'react'; import { Alert } from "antd"; import {observer, inject} from 'mobx-react' @inject('appStore') @observer export default class TestAntd extends Component { static defaultProps = { value: 'test static' } render() { const {value, appStore} = this.props return <div> <div>count: {appStore.count}</div> <button onClick={() => appStore.updateCount(1)}>++</button> <button onClick={() => appStore.updateCount(-1)}>--</button> <Alert message="我是独立的 TestAntd 组件" type="success" /> <div>{value}</div> </div> } } // TestRouter/index.js import React, {Component} from 'react'; import { Alert } from "antd"; import {observer, inject} from 'mobx-react' @inject('appStore') @observer export default class TestRouter extends Component { render() { const {appStore} = this.props return <div> <div>count: {appStore.count}</div> <button onClick={() => appStore.updateCount(1)}>++</button> <button onClick={() => appStore.updateCount(-1)}>--</button> <Alert message="我是独立的 TestRouter 组件" type="info" /> </div> } } %%CODEBLOCK68%% sh npm i axios @babel/polyfill --save cd src && mkdir bootstrap api common touch bootstrap/index.js touch bootstrap/http-interceptors.js touch api/test.js touch store/ui.js %%CODEBLOCK69%% js // src/index.js 添加两行代码 import './bootstrap' import "@babel/polyfill"; %%CODEBLOCK70%% js // api/test.js /** * {loading: true} 用于监控全局请求个数, 处理过程在 http-interceptors.js 中 */ import axios from 'axios' export default { testGet(){ return axios.get('/api/testGet', {loading: true}) }, testPost(params){ return axios.post('/api/testPost', params, {loading: true}) }, testDelete(params){ return axios.delete('/api/testDelete', {data: params}) } } %%CODEBLOCK71%% js // bootstrap/index.js render之前进行注入的一些代码, 例如请求拦截器 import './http-interceptors' %%CODEBLOCK72%% js // bootstrap/http-interceptors.js 相关错误处理和业务逻辑自行补全 import axios from 'axios' import {uiStore} from '../store' axios.defaults.baseURL = "/" // token 验证, 需要的话自行打开注释 // axios.defaults.headers.common['Authorization'] = AUTH_TOKEN; // 添加请求拦截器 axios.interceptors.request.use(config => { console.log('%c request config', 'font-size:21pt;color:green', config) // 发送请求之前做的事情 if(config.loading === true){ uiStore.updateReqCount(+config.loading) } return config }, error => { console.log('%c request error', 'font-size:21pt;color:red', error) // 对请求错误处理 uiStore.updateReqCount(-1) return Promise.reject(error) }) /** * 后端返回的数据格式 * { * code: 0, * msg: '这是一条成功的消息, code为0, 其他code根据需求自定义', * data: {...} * } */ // 添加响应拦截器 axios.interceptors.response.use(res => { console.log('%c response res', 'font-size:21pt;color:blue', res) // 对响应数据处理 if(res && res.config && res.config.loading === true){ uiStore.updateReqCount(-1) } const result = res.data if(result.hasOwnProperty('code') && result.code !== 0){ // 根据需求自定义错误码, 统一处理 } return result.data }, error => { console.log('%c response error', 'font-size:21pt;color:red', error) // 对响应错误处理 uiStore.updateReqCount(-1) return Promise.reject(error) }) const handleError = (error) => { if(error instanceof Error){ console.log('[ERROR]:', error) } } %%CODEBLOCK73%% js /** * store/ui.js * UI Store中常见存储的信息有: * Session 信息 * 不会再后端存储的信息 * 会全局影响UI的信息: * Window尺寸 * 提示消息 * 当前语言 * 当前主题 * 更多可能存储的组件信息: * 当前选择 * 工具条显示隐藏状态 */ import {observable, action} from 'mobx' export default class UiStore { // 表示在一时间段内请求的个数, 可用做全局 loading @observable reqCount = 0 @action updateReqCount(num = 0){ this.reqCount = this.reqCount + num } } %%CODEBLOCK74%% jsx import React, {Component} from 'react'; import { Alert, Button } from "antd"; import {observer} from 'mobx-react' import testApi from '../../api/test' import {uiStore} from '../../store' import './index.css' @observer export default class TestApi extends Component { async testGet(){ const result = await testApi.testGet() console.log('TestApi testGet result:', result) } async testPost(){ const result = await testApi.testPost({}) console.log('TestApi testGet result:', result) } async testDelete(){ const result = await testApi.testDelete({}) console.log('TestApi testGet result:', result) } render() { return <div className="container"> <div> <p>reqCount: {uiStore.reqCount}</p> <Button type="primary" onClick={() => this.testGet()}>testGet</Button> <Button onClick={() => this.testPost()}>testPost</Button> <Button onClick={() => this.testDelete()}>testDelete</Button> </div> </div> } } %%CODEBLOCK75%% js // easy-mock.com 免费的造假数据的网站 const ProxyUrl = "https://easy-mock.com/mock/5b8c9f2fdcc57313cd5b6678" module.exports = WebpackMerge(CommonConfig, { // ... devServer: { // ... proxy: { "/api/*": { target: ProxyUrl, changeOrigin: true, secure: false } } } }); %%CODEBLOCK76%% js // store/index.js import UiStore from './ui' export const uiStore = new UiStore() %%CODEBLOCK77%% jsx // router/index.js const TestApi = Loadable({ loader: () => import('../component/Hello/TestApi'), loading: Loading }) <li><Link to="/testapi">TestApi</Link></li> <Route exact path="/testapi" component={TestApi}/> %%CODEBLOCK78%% sh mkdir src/component/NotFound touch src/component/NotFound/index.js %%CODEBLOCK79%% jsx // NotFount/index.js import React, {PureComponent} from 'react' export default class NotFound extends PureComponent { render(){ return <div style={{color: 'red', fontSize: 88}}>404</div> } } %%CODEBLOCK80%% jsx // router/index.js <Switch> // ... <Route component={NotFound}/> </Switch> %%CODEBLOCK81%% sh npm i mini-css-extract-plugin less less-loader --save-dev %%CODEBLOCK82%% json { // ... "scripts": { "dev": "webpack-dev-server --config webpack.dev.js --color --progress", "build": "webpack --config webpack.prod.js --color --progress" } // ... } %%CODEBLOCK83%% js const path = require("path"); const webpack = require("webpack"); const HtmlWebpackPlugin = require("html-webpack-plugin"); const CleanWebpackPlugin = require('clean-webpack-plugin'); // 如果预先定义过环境变量,就将其赋值给ASSET_PATH变量,否则赋值为根目录 const ASSET_PATH = process.env.ASSET_PATH || '/'; module.exports = { output: { path: path.join(__dirname, "./dist"), filename: "[name].[chunkhash].js", publicPath: ASSET_PATH }, module: { rules: [ { test: /\.js$/, // 正则匹配以 .js 结尾的文件来使用 babel 解析 use: ["babel-loader?cacheDirectory=true"], // cacheDirectory是用来缓存编译结果,下次编译加速 include: path.join(__dirname, "src") // 需要解析的目录 }, { test: /\.(png|jpg|gif)$/, use: [ { loader: "url-loader", options: { // olimit 8192意思是,小于等于8K的图片会被转成base64编码,直接插入HTML中,减少HTTP请求 limit: 8192 } } ] } ] }, plugins: [ new CleanWebpackPlugin(['dist']), new HtmlWebpackPlugin({ filename: "index.html", template: path.join(__dirname, "src/index.html") }), new webpack.HashedModuleIdsPlugin() ], optimization: { runtimeChunk: "single", splitChunks: { cacheGroups: { vendor: { test: /[\\/]node_modules[\\/]/, name: "vendors", chunks: "all" } } } } }; %%CODEBLOCK84%% js const path = require("path"); const WebpackMerge = require("webpack-merge"); const CommonConfig = require("./webpack.common"); const ProxyUrl = "https://easy-mock.com/mock/5b8c9f2fdcc57313cd5b6678"; module.exports = WebpackMerge(CommonConfig, { devtool: "inline-source-map", mode: "development", module: { rules: [ { test: /\.css$/, use: ["style-loader", "css-loader"] }, { test: /\.less$/, use: ["style-loader", "css-loader", "less-loader"] } ] }, devServer: { contentBase: path.join(__dirname, "dist"), historyApiFallback: true, // 指定使用一个 host。默认是 localhost。如果你希望服务器外部可访问,写法如下 host: "0.0.0.0", port: 9090, proxy: { "/api/*": { target: ProxyUrl, changeOrigin: true, secure: false } } } }); %%CODEBLOCK85%% js const WebpackMerge = require("webpack-merge"); const CommonConfig = require('./webpack.common'); const MiniCssExtractPlugin = require("mini-css-extract-plugin"); module.exports = WebpackMerge(CommonConfig, { mode: 'production', module: { rules: [ { test: /\.css$/, use: [MiniCssExtractPlugin.loader, "css-loader"] }, { test: /\.less$/, use: [MiniCssExtractPlugin.loader, "css-loader", 'less-loader'] } ] }, plugins: [ new MiniCssExtractPlugin({ filename: "[name].css", chunkFilename: "[id].css" }) ] }) %%CODEBLOCK86%% sh # 因为sass-loader依赖于node-sass,所以需要先安装node-sass npm i sass node-sass --save-dev npm i sass-loader --save-dev %%CODEBLOCK87%% js { test: /\.s(a|c)ss$/, use: ["style-loader","css-loader","sass-loader"] } %%CODEBLOCK88%% js { test: /\.s(a|c)ss$/, use: [MiniCssExtractPlugin.loader,"css-loader","sass-loader"] } %%CODEBLOCK89%% sh cd src mkdir pages cd common && touch utils.js contants.js %%CODEBLOCK90%% sh npm i react-intl intl -save // 创建相关文件 cd src && mkdir I18N && touch app.js cd I18N && touch en_US.js zh_CN.js %%CODEBLOCK91%% js // src/app.js import React, { Component } from "react"; import getRouter from "./router"; import { IntlProvider, addLocaleData } from "react-intl"; import { uiStore } from "./store"; import { observer } from "mobx-react"; import zh from 'react-intl/locale-data/zh' import en from 'react-intl/locale-data/en' addLocaleData([...en, ...zh]) @observer export default class App extends Component { getLocale() { let result = {}; switch (uiStore.language) { case "zh": result = require("./I18N/zh_CN"); break; case "en": result = require("./I18N/en_US"); break; default: result = require("./I18N/zh_CN"); } return result.default || result; } render() { console.log('uiStore.language:', uiStore.language) const messages = this.getLocale() return ( <IntlProvider locale={uiStore.language} messages={messages}> {getRouter()} </IntlProvider> ); } } %%CODEBLOCK92%% js // src/I18N/zh_CN.js export default { hello: '这是一段放在国际化文件中的中文.', withParams: '这是一段带有参数的国际化文案, { param }' } // src/I18N/en_US.js export default { hello: 'This is a paragraph in English.', withParams: 'This is an international copy with parameters, {param}' } %%CODEBLOCK93%% js @observable language = 'zh' @action updateLanguage(lang){ this.language = lang } %%CODEBLOCK94%% js // Hello/TestI18N.js 内容如下 import React, { Component } from "react"; import { FormattedMessage, FormattedDate, FormattedTime } from "react-intl"; import { uiStore } from "../../store"; import { Button } from "antd"; export default class TestI18N extends Component { render() { return ( <div> <Button onClick={() => uiStore.updateLanguage('en')}>to en</Button> <Button onClick={() => uiStore.updateLanguage('zh')}>to zh</Button> <div> <FormattedMessage id="hello" /> </div> <div> <FormattedMessage id="withParams" values={{ param: "[我是param]" }} /> </div> <div> <FormattedDate value={Date.now()}/> </div> <div> <FormattedTime value={Date.now()}/> </div> </div> ); } } %%CODEBLOCK95%% js const TestI18N = Loadable({ loader: () => import('../component/Hello/TestI18N'), loading: Loading }) <li><Link to="/i18n">i18n</Link></li> <Route exact path="/i18n" component={TestI18N}/> %%CODEBLOCK96%% sh npm i i18n-js lscache --save // 创建相关文件 cd src && mkdir I18N cd I18N && touch index.js en_US.js zh_CN.js %%CODEBLOCK97%% js export const LOCAL_LANGUAGE = 'local_language' %%CODEBLOCK98%% js // index.js import lscache from 'lscache' import {LOCAL_LANGUAGE} from '../common/constants' import I18N from 'i18n-js' import en_US from './en_US' import zh_CN from './zh_CN' I18N.fallbacks = true I18N.translations = { en_US, zh_CN } I18N.defaultLocale = 'zh_CN' /** * 存放在 localstorage 中, 默认 'zh_CN' * 设置是只需要 lscache.set(LOCAL_LANGUAGE, 'en_US'), 并刷新页面 */ const local = lscache.get(LOCAL_LANGUAGE) I18N.locale = local ? local : 'zh_CN' export default I18N // en_US.js export default { hello: 'This is a paragraph in English.', withParams: 'This is an international copy with parameters, {{param}}' } // zh_CN.js export default { hello: '这是一段放在国际化文件中的中文.', withParams: '这是一段带有参数的国际化文案, {{param}}' } %%CODEBLOCK99%% js import i18n from '../I18N' window.i18n = global.i18n = i18n %%CODEBLOCK100%% js import React, {Component} from 'react'; import { Alert, Button } from "antd"; import lscache from 'lscache' import {LOCAL_LANGUAGE} from '../../common/constants' export default class Hello extends Component { render() { return <div> 我是独立的 Hello 组件 <div>{i18n.t('hello')}</div> <Alert message={i18n.t('withParams', {param: '--这个是参数--'})} type="success" /> <Button onClick={() => lscache.set(LOCAL_LANGUAGE, 'en_US')}>en_US</Button> <div> <div>6kb</div> <img src={require('../../assets/images/antd6kb.png')} /> <div>10kb</div> <img src={require('../../assets/images/antd10kb.png')} /> </div> </div> } } ``

相关代码 branch: i18n-v1

what performance: { hints: false }