好久之前搭建的一个react执行环境,受历史影响是webpack3.10.0和webpack-dev-server2.7.1的环境,新项目准备用webpack4重新弄弄了,旧的记录就合并发布了(在没有快速目录的地方写的,之前是分为了10个文件)
所使用的一些包可能进过不断的迭代升级已不支持
01.初始化项目(安装需要的包)
//生成package.json
npm init
安装基础包
npm install react react-dom --save
npm install webpack@3.10.0 webpack-dev-server@2.7.1 --save-dev
<!--webpack和webpack-dev-server同时全局安装一下-->
npm install webpack@3.10.0 wepack-dev-server@2.7.1 -g
安装和配置babel
npm install
babel-core 核心功能
babel-loader
babel-polyfill 转换低版本代码
babel-preset-env 解析Es6
babel-preset-react 解析JSX
--save-dev
安装loader包
npm install
css-loader
style-loader
file-loader
url-loader
image-webpack-loader
html-loader
--save-dev
- --save 会把依赖包名称添加到 package.json 文件 dependencies
dependencies是运行时依赖,是我们发布后还需要依赖的模块
- --save-dev 则添加到 package.json 文件 devDependencies
devDependencies是开发时的依赖,在发布后用不到它,而只是在我们开发才用到它
当npm install时,会下载dependencies和devDependencies中的模块,当使用npm install –production或者注明NODE_ENV变量值为production时,只会下载dependencies中的模块
02.执行程序,创建文件
创建/.babelrc
//.babelrc
{
"presets": [
"react",
"env"
]
}
创建webpack.config.js
const path = require('path');
module.exports = {
context: path.resolve(__dirname, 'app'),
resolve: {
extensions: ['.js', '.jsx'], //后缀名自动补全,可以使用jsx的文件后缀
modules: [
path.resolve(__dirname, 'node_modules')
]
},
entry: './app.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'app.js',
},
module: {
rules: [
{
test: /\.(js|jsx)$/,
exclude: /(node_modules|bower_components)/,
use: {
loader: 'babel-loader',
options: {
presets: ['env', 'react'],
}
}
},
{
test: /index\.html/,
use: [{
loader: 'file-loader',
options: {
name (file) {
return 'index.[ext]'
}
}
}]
}
]
},
};
创建/app/component/App.js
import React from 'react';
import Header from './Header';
import Content from './Content';
import Sidebar from './Sidebar';
class App extends React.Component {
render() {
return (
<div className="app">
<Header/>
<Sidebar/>
<Content/>
</div>
)
}
}
export default App;
创建/app/component/Header.js
import React from 'react';
class Header extends React.Component {
render() {
return (
<div>this is header</div>
)
}
}
export default Header;
创建/app/component/Sidebar.js
import React from 'react';
class Sidebar extends React.Component {
render() {
return (
<div className="sidebar">
this is sidebar
</div>
)
}
}
export default Sidebar;
创建/app/component/Content.js
import React from 'react';
class Content extends React.Component {
render() {
return (
<div className="content">
this is content
</div>
)
}
}
export default Content;
创建/app/app.js入口文件
import './index.html';
import React from 'react';
import ReactDom from 'react-dom';
import App from './component/App';
ReactDom.render(
<App/>, document.getElementById('root')
);
创建/app/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<base href="/">
<meta charset="UTF-8">
<title>hello world</title>
</head>
<body>
<div id="root"></div>
<script src="./app.js"></script>
</body>
</html>
执行webpack打包
<!--控制台执行webpack会在/dist中生成一个app.js和index.html-->
webpack
开启本地服务器
修改webpack.config.js
//webpack-dev-server配置
devServer: {
contentBase: './dist',
historyApiFallback: true,
inline: true,//源文件改变,会自动刷新页面
port: 1234,//设置默认监听端口,如果省略,默认为"8080"
},
修改package.json
"scripts": {
"start": "webpack-dev-server"
},
执行start
npm start
03.添加css样式
新建/style/app.css
html, body, #root, .app {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
}
div {
box-sizing: border-box;
}
.header {
position: fixed;
top: 0;
left: 150px;
right: 0;
padding: 0 30px;
height: 61px;
background: #ebebeb;
}
.title {
width: 90%;
margin: 0 5%;
height: 80px;
line-height: 80px;
color: #fff;
font-size: 20px;
text-align: center;
border-bottom: 1px solid rgba(255, 255, 255, 0.2);
}
.sidebar {
position: fixed;
top: 0;
bottom: 0;
left: 0;
width: 150px;
background: #3d3c4a;
}
.header-con {
position: relative;
width: 100%;
height: 60px;
}
.user {
position: absolute;
right: 0;
top: 15px;
height: 30px;
line-height: 30px;
color: gray;
}
.text {
float: left;
height: 30px;
}
.avatar {
float: right;
width: 30px;
height: 30px;
}
.avatar img {
width: 100%;
height: 100%;
border-radius: 90%;
}
.content {
position: fixed;
top: 60px;
left: 150px;
right: 0;
bottom: 0;
padding: 30px;
color: gray;
}
修改/app/component/Header.js
import React from 'react';
class Header extends React.Component {
render() {
return (
<div className="header">
<div className="header-con">
<div className="user">
欢迎您!
</div>
</div>
</div>
)
}
}
export default Header;
修改/app/component/Sidebar.js
import React from 'react';
class Sidebar extends React.Component {
render() {
return (
<div className="sidebar">
<div className="title">
easterCat
</div>
</div>
)
}
}
export default Sidebar;
修改/app/component/App.js
...
import '../style/app.css';
添加css依赖/webpack.config.js
module: {
rules: [
{
test: /\.css$/,
use: [{
loader: 'style-loader'
}, {
loader: 'css-loader'
}],
},
]
},
打包运行
webpack
此时的css打包是自动添加到index的style标签里面
04.添加图片的依赖
创建/app/images/avatar.jpg
修改/app/component/Header.js
import React from 'react';
import Avatar from '../images/avatar.jpg';
class Header extends React.Component {
render() {
return (
<div className="header">
<div className="header-con">
<div className="user">
<span className="text">欢迎您!</span>
<span className="avatar">
<img src={Avatar} alt="" />
</span>
</div>
</div>
</div>
)
}
}
export default Header;
添加图片依赖/webpack.config.js
module: {
rules: [
{
test: /.*\.(gif|png|jpe?g|svg)$/,
use: [{
loader: 'url-loader',
options: {
limit: 10000,
name: 'images/[name].[ext]'
}
}, {
loader: 'image-webpack-loader'
}]
},
]
},
05.添加预编译语言-sass
<!--less和sass都用,此处用sass-->
npm install sass-loader node-sass --save-dev
修改webpack.config.js
module: {
rules:[{
test: /\.scss$/,
use: [{
loader: 'style-loader'
}, {
loader: 'css-loader',
options: {
sourceMap: true
}
}, {
loader: 'sass-loader',
options: {
sourceMap: true,
outputStyle: 'expanded',
sourceMapContents: true
}
}]
},]
},
添加app.scss
@import 'body';
@import 'header';
@import 'sidebar';
@import 'content';
将app.css拆分成body.scss/header.scss/sidebar.scss/content.scss
<!--例:header.scss-->
.header {
position: fixed;
top: 0;
left: 150px;
right: 0;
padding: 0 30px;
height: 61px;
background: #ebebeb;
.user {
position: absolute;
right: 0;
top: 15px;
height: 30px;
line-height: 30px;
color: gray;
}
.header-con {
position: relative;
width: 100%;
height: 60px;
}
.text {
float: left;
height: 30px;
}
.avatar {
float: right;
width: 30px;
height: 30px;
img {
width: 100%;
height: 100%;
border-radius: 90%;
}
}
}
修改/app/component/app.js
import '../style/app.css';
<!--修改为-->
import '../style/app.scss';
重新打包
webpack
安装第二个预编译语言less
npm install less less-loader --save-dev
修改webpack.config.js
module: {
rules: [
{
test: /\.less$/,
use: [{
loader: 'style-loader'
}, {
loader: 'css-loader',
options: {
sourceMap: true
}
}, {
loader: 'less-loader',
options: {
sourceMap: true,
outputStyle: 'expanded',
sourceMapContents: true,
}
}]
},
]
},
06.添加一个ui框架antd-design
npm install antd --save
<!--babel-plugin-import必须要装-->
npm install babel-plugin-import --save-dev
引入antd样式
<!--在App.js中引入-->
import 'antd/dist/antd.css' 或 import 'antd/dist/antd.less'
<!--在app.scss中引入-->
@import "~antd/dist/antd.css";
使用组件,修改content.js
import React from 'react';
import Table from 'antd/lib/Table';
class Content extends React.Component {
render() {
const columns = [{
title: '姓名',
dataIndex: 'name',
key: 'name',
}, {
title: '年龄',
dataIndex: 'age',
key: 'age',
}, {
title: '电话号码',
dataIndex: 'number',
key: 'number',
}, {
title: '邮箱',
dataIndex: 'email',
key: 'email',
}];
const data = [];
for (let i = 1; i < 15; i++) {
let obj = {
name: 'doudou',
age: 32,
number: 123456789,
email: '123456789@163.com',
};
obj.key = i;
data.push(obj);
}
return (
<div className="content">
<Table columns={columns} dataSource={data}/>
</div>
)
}
}
export default Content;
babel-plugin-import插件
从 antd 引入模块即可,无需引入样式,babel-plugin-import 会加载 JS 和 CSS
修改.babelrc
//.babelrc
{
"plugins": [
[
"import",
{
"libraryName": "antd",
"style": true //引入less,如果使用css文件就改为'css'
}
]
]
}
修改/app/component/Content.js
import {Table} from 'antd';
此时app.scss和App.js中引入的antd.css可以去掉了
问题
使用antd-design
更改/app/component/App.js
import React from 'react';
import Header from './layout/Header';
import Content from './layout/Content';
import Sidebar from './layout/Sidebar';
import '../style/app.scss';
import {Layout} from 'antd';
class App extends React.Component {
constructor() {
super();
this.state = {
collapsed: false
};
this.toggle = () => {
this.setState({
collapsed: !this.state.collapsed
})
};
}
render() {
return (
<Layout className="layout-app">
<Layout.Sider
trigger={null}
collapsible
collapsed={this.state.collapsed}
>
<Sidebar/>
</Layout.Sider>
<Layout>
<Layout.Header style={{background: '#fff', padding: 0}}>
<Header collapsed={this.state.collapsed}
toggle={this.toggle}
/>
</Layout.Header>
<Layout.Content style={{margin: '24px 16px', padding: 24, background: '#fff', minHeight: 280}}>
<Content/>
</Layout.Content>
</Layout>
</Layout>
)
}
}
export default App;
修改/app/component/layout/Header.js
import React from 'react';
import avatar_img from '../../images/avatar.jpg';
import {Icon, Avatar} from 'antd';
class Header extends React.Component {
constructor(props) {
super(props);
}
render() {
const {
collapsed,
toggle
} = this.props;
return (
<div className="layout-header">
<Icon
className="trigger"
type={collapsed ? 'menu-unfold' : 'menu-fold'}
onClick={toggle}
/>
<Avatar className="avatar"
src={avatar_img}/>
</div>
)
}
}
export default Header;
修改/app/component/layout/Sidebar.js
import React from 'react';
import {Menu, Icon} from 'antd';
class Sidebar extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div className="layout-sidebar">
<div className="logo"/>
<Menu theme="dark" mode="inline" defaultSelectedKeys={['1']}>
<Menu.Item key="1">
<Icon type="user"/>
<span>nav 1</span>
</Menu.Item>
<Menu.Item key="2">
<Icon type="video-camera"/>
<span>nav 2</span>
</Menu.Item>
<Menu.Item key="3">
<Icon type="upload"/>
<span>nav 3</span>
</Menu.Item>
</Menu>
</div>
)
}
}
export default Sidebar;
修改/app/component/layout/Content.js
import React from 'react';
import {Table} from 'antd';
class Content extends React.Component {
render() {
const columns = [{
title: '姓名',
dataIndex: 'name',
key: 'name',
}, {
title: '年龄',
dataIndex: 'age',
key: 'age',
}, {
title: '电话号码',
dataIndex: 'number',
key: 'number',
}, {
title: '邮箱',
dataIndex: 'email',
key: 'email',
}];
const data = [];
for (let i = 1; i < 15; i++) {
let obj = {
name: 'doudou',
age: 32,
number: 123456789,
email: '123456789@163.com',
};
obj.key = i;
data.push(obj);
}
return (
<Table columns={columns} dataSource={data}/>
)
}
}
export default Content;
新建layout.scss
.layout-app {
height: 100%;
}
.layout-sidebar {
.logo {
height: 32px;
background: #333;
border-radius: 6px;
margin: 16px;
}
}
.layout-header {
.trigger {
font-size: 18px;
line-height: 64px;
padding: 0 16px;
cursor: pointer;
transition: color .3s;
&:hover {
color: #108ee9;
}
}
.avatar {
float: right;
margin-top: 16px;
margin-right: 16px;
cursor: pointer;
}
}
07.引入路由react-router
安装react-router
npm install react-router-dom --save
修改/app/app.js
import './index.html';
import './styles/app.less';
import React from 'react';
import ReactDom from 'react-dom';
import App from './components/App.jsx';
import {BrowserRouter, Route} from 'react-router-dom';
ReactDom.render(
<BrowserRouter>
<Route path="/" component={App}/>
</BrowserRouter>
,
document.getElementById('root')
);
修改/app/component/App.js
import React from 'react';
import Home from './Home';
import Login from './Login';
import {Route} from 'react-router-dom';
class App extends React.Component {
componentDidMount() {
const {
location,
history
} = this.props;
if (location.pathname === '/home' || location.pathname === '/') {
history.replace('/home')
} else if (location.pathname === '/login') {
history.replace('/login')
}
}
render() {
return (
<div className="app">
<Route path="/home" component={Home}/>
<Route path="/login" component={Login}/>
</div>
)
}
}
export default App;
添加/app/component/home/Home.js
import React from 'react';
import {matchPath} from 'react-router-dom';
import Header from './Header';
import Content from './Content';
import Sidebar from './Sidebar';
import {Layout} from 'antd';
export default class Home extends React.Component {
constructor(props) {
super(props);
this.state = {
collapsed: false
};
this.ress = ['content01', 'content02', 'content03'];
this.res = null;
const match = matchPath(this.props.history.location.pathname, {
path: '/home/:res'
});
if (match) {
this.res = match.params.res;
}
this.toggle = () => {
this.setState({
collapsed: !this.state.collapsed
})
};
}
componentWillMount() {
if (!this.res || !this.res.length || this.ress.indexOf(this.res) === -1) {
this.props.history.replace(`/home/content01`)
}
}
render() {
return (
<Layout className="layout-app">
<Layout.Sider
trigger={null}
collapsible
collapsed={this.state.collapsed}
>
<Sidebar res= {this.res}/>
</Layout.Sider>
<Layout>
<Layout.Header style={{background: '#fff', padding: 0}}>
<Header collapsed={this.state.collapsed}
toggle={this.toggle}
/>
</Layout.Header>
<Layout.Content style={{margin: '24px 16px', padding: 24, background: '#fff', minHeight: 280}}>
<Content/>
</Layout.Content>
</Layout>
</Layout>
)
}
}
添加/app/component/login/Login.js
import React from 'react';
import {Form, Icon, Input, Button, Checkbox} from 'antd';
const FormItem = Form.Item;
class Logining extends React.Component {
constructor(props) {
super(props);
this.handleSubmit = (e) => {
const {
history
} = this.props;
e.preventDefault();
this.props.form.validateFields((err, values) => {
if (!err) {
if (values.userName === 'admin' && values.password === '123456') {
history.replace('/home')
}
console.log('Received values of form: ', values);
}
});
}
}
render() {
const {
getFieldDecorator
} = this.props.form;
return (
<div className="login">
<Form onSubmit={this.handleSubmit} className="login-form">
<FormItem>
{getFieldDecorator('userName', {
rules: [{required: true, message: 'Please input your username!'}],
})(
<Input prefix={<Icon type="user" style={{fontSize: 13}}/>} placeholder="Username"/>
)}
</FormItem>
<FormItem>
{getFieldDecorator('password', {
rules: [{required: true, message: 'Please input your Password!'}],
})(
<Input prefix={<Icon type="lock" style={{fontSize: 13}}/>} type="password"
placeholder="Password"/>
)}
</FormItem>
<FormItem>
{getFieldDecorator('remember', {
valuePropName: 'checked',
initialValue: true,
})(
<Checkbox>Remember me</Checkbox>
)}
<a className="login-form-forgot" href="">Forgot password</a>
<Button type="primary" htmlType="submit" className="login-form-button">
Log in
</Button>
Or <a href="">register now!</a>
</FormItem>
</Form>
</div>
);
}
}
const Login = Form.create()(Logining);
export default Login;
修改/app/component/layout/Sidebar.js
import React from 'react';
import {Menu, Icon} from 'antd';
import {NavLink} from 'react-router-dom';
export default class Sidebar extends React.Component {
render() {
const {
res
} = this.props;
return (
<div className="layout-sidebar">
<div className="logo"/>
<Menu theme="dark"
mode="inline"
defaultSelectedKeys={[res]}
>
<Menu.Item key="content01">
<NavLink to="/home/content01">
<Icon type="user"/>
<span>nav 1</span>
</NavLink>
</Menu.Item>
<Menu.Item key="content02">
<NavLink to="/home/content02">
<Icon type="video-camera"/>
<span>nav 2</span>
</NavLink>
</Menu.Item>
<Menu.Item key="content03">
<NavLink to="/home/content03">
<Icon type="upload"/>
<span>nav 3</span>
</NavLink>
</Menu.Item>
</Menu>
</div>
)
}
}
修改/app/component/layout/Content.js
import React from 'react';
import {Route} from 'react-router-dom';
import Content01 from './content01';
import Content02 from './content02';
import Content03 from './content03';
export default class Content extends React.Component {
render() {
return (
<div>
<Route path="/home/Content01" component={Content01}/>
<Route path="/home/Content02" component={Content02}/>
<Route path="/home/Content03" component={Content03}/>
</div>
)
}
}
修改/app/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<base href="/">
<meta charset="UTF-8">
<title>hello world</title>
</head>
<body>
<div id="root"></div>
<script src="./app.js"></script>
</body>
</html>
08.引入状态管理react-redux
安装redux
npm i redux react-redux --save
新建/app/store/store.js
import {createStore} from 'redux';
import user from '../components/content01.reducer';
const store = createStore(user);
export default store;
修改/app/app.js
import {Provider} from 'react-redux';
import store from './store/store';
ReactDom.render(
<Provider store={store}>
<BrowserRouter>
<Route path="/" component={App}/>
</BrowserRouter>
</Provider>
,
document.getElementById('root')
);
新建/app/component/content01.reducer.js
let initState = {
data: [{
name: 'doudou',
age: 32,
phone: 123456789,
email: '123456789@163.com',
key: 1,
}]
};
function user(state = initState, action) {
const data = state.data;
switch (action.type) {
case 'ADD_ONE_USER':
data[data.length] = action.payload;
return {data: data};
default:
return state;
}
}
export default user;
新建/app/component/content01.action.js
export function addOneUser(value) {
return {
type: 'ADD_ONE_USER',
payload: value
}
}
修改/app/component/content01.jsx
import React from 'react';
import {Table, Icon, Button} from 'antd';
import {connect} from 'react-redux';
import {addOneUser} from './content01.action';
import FromContent from './FromContent';
class Content01 extends React.Component {
constructor(props) {
super(props);
this.state = {
visible: false,
date: null
};
this.showModal = () => {
this.setState({
visible: true,
data: new Date()
});
};
this.closeModal = () => {
this.setState({
visible: false,
});
};
this.submit = (values) => {
values.key = Date.parse(new Date());
this.props.addOneUser(values);
this.closeModal();
}
}
render() {
const columns = [{
title: '姓名',
dataIndex: 'name',
key: 'name',
}, {
title: '年龄',
dataIndex: 'age',
key: 'age',
}, {
title: '电话号码',
dataIndex: 'phone',
key: 'phone',
}, {
title: '邮箱',
dataIndex: 'email',
key: 'email',
}];
const data = this.props.data;
return (
<div>
<Button type="primary" onClick={this.showModal}>
<Icon type="user-add"/>添加
</Button>
<Table columns={columns} dataSource={data}/>
{
this.state.visible ? <FromContent date={this.state.date}
submit={this.submit}
showModal={this.showModal}
closeModal={this.closeModal}
/> : null
}
</div>
)
}
}
const mapStateToProps = (state) => {
return {
data: state.data
}
};
const mapDispatchToProps = (dispatch) => {
return {
addOneUser: (value) => dispatch(addOneUser(value))
}
};
export default connect(mapStateToProps, mapDispatchToProps)(Content01);
新建/app/component/FromContent.js
import React from 'react';
import {Modal, Button, Form, Input} from 'antd';
const FormItem = Form.Item;
import {increaseAction} from './content01.action';
class FromContent01 extends React.Component {
constructor(props) {
super(props);
this.handleSubmit = (e) => {
e.preventDefault();
this.props.form.validateFields((err, values) => {
if (!err) {
console.log('Received values of form: ', values);
this.props.submit(values);
}
});
}
}
render() {
const {getFieldDecorator} = this.props.form;
const {closeModal, date} = this.props;
return (
<Modal
title="添加一个成员"
key={date}
visible={true}
onCancel={closeModal}
footer={null}
>
<Form onSubmit={this.handleSubmit} className="login-form">
<FormItem label="姓名">
{getFieldDecorator('name', {
rules: [{required: true, message: 'Please input your name!'}],
})(
<Input placeholder="姓名"/>
)}
</FormItem>
<FormItem label="年龄">
{getFieldDecorator('age', {
rules: [{required: true, message: 'Please input your age!'}],
})(
<Input placeholder="年龄"/>
)}
</FormItem>
<FormItem label="电话号码">
{getFieldDecorator('phone', {
rules: [{
required: true, message: 'Please input your phone!',
}],
})(
<Input type="电话号码"/>
)}
</FormItem>
<FormItem label="邮箱">
{getFieldDecorator('email', {
rules: [{
required: true, message: 'Please input your email!',
}],
})(
<Input type="邮箱"/>
)}
</FormItem>
<FormItem>
<Button type="primary" htmlType="submit" className="login-form-button">
Log in
</Button>
</FormItem>
</Form>
</Modal>
)
}
}
const FromContent = Form.create()(FromContent01);
export default FromContent;
09.更好的使用redux
redux-freeze运行时报错机制
redux-thunkredux的异步中间件
redux-logger日志中间件
redux-immutable在不可变数据类型中合并reducer
redux-create-reducer使用action和reducer的关联变的简单
安装依赖插件
npm i --save redux-immutable redux-freeze redux-thunk redux-logger immutable redux-create-reducer
修改/app/store/store.js
import {createStore, applyMiddleware, compose} from 'redux';
import {combineReducers} from 'redux-immutable';
import freeze from "redux-freeze"
import thunk from 'redux-thunk'
import logger from 'redux-logger'
import {Map} from 'immutable';
import reducers from './reducers';
let middlewares = [];
middlewares.push(thunk);
middlewares.push(logger);
middlewares.push(freeze);
//添加中间件
let middleware = applyMiddleware(...middlewares);
//添加redux dev tools,可以在谷歌商城里直接安装工具,搜索名字
middleware = compose(middleware, window.devToolsExtension());
const reducer = combineReducers(reducers);
const store = createStore(
reducer,
Map({}),
middleware
);
export default store;
新建/app/store/reducers.js
import user from '../components/content01.reducer';
export default {
user
}
修改/app/component/content01.reducer.js
import {createReducer} from 'redux-create-reducer';
import {fromJS} from 'immutable';
import {
ADD_ONE_USER
} from './content01.actions';
const initState = fromJS({
data: [{
name: 'doudou',
age: 32,
phone: 123456789,
email: '123456789@163.com',
key: 1,
}]
});
const handlers = {
[ADD_ONE_USER]: (user, action) => {
return user.set('data', user.get('data').push(fromJS(action.payload)));
}
};
export default createReducer(initState, handlers);
修改/app/component/content01.action.js
export const ADD_ONE_USER = 'ADD_ONE_USER';
export function addOneUser(value) {
return dispatch => {
return dispatch({
type: 'ADD_ONE_USER',
payload: value
})
}
}
修改/app/component/content01.js
const mapStateToProps = (state) => {
return {
data: state.get('user').get('data'),
}
};
const mapActionCreators = {
addOneUser
};
export default connect(mapStateToProps, mapActionCreators)(Content01);
10.优化环境
下载需要的依赖
npm i
extract-text-webpack-plugin
copy-webpack-plugin
clean-webpack-plugin
webpack-merge
html-webpack-plugin
postcss-smart-import
postcss-loader
precss
--save-dev
将webpack.config.js拆分
webpack.common.js
const path = require('path');
const ROOTPATH = path.resolve(__dirname, '.');
module.exports = {
context: path.resolve(ROOTPATH, 'app'),
resolve: {
extensions: ['.js', '.jsx'], //后缀名自动补全,可以使用jsx的文件后缀
modules: [path.resolve(ROOTPATH, "node_modules")],
alias: {
COMPONENTS_PATH: './components',
}
},
entry: {
app: ['babel-polyfill', './app.js']
},
module: {
rules: [
{
test: /\.(js|jsx)$/,
exclude: /(node_modules|bower_components)/,
use: {
loader: 'babel-loader',
options: {
presets: ['env', 'react'],
}
}
},
{
test: /.*\.(gif|png|jpe?g|svg)$/,
use: [{
loader: 'url-loader',
options: {
limit: 10000,
name: 'images/[name].[ext]'
}
}, {
loader: 'image-webpack-loader'
}]
},
{
test: /\.html$/,
use: [{
loader: 'html-loader',
options: {
minimize: false
}
}],
},
{
test: /\.(woff|svg|eot|ttf)\??.*$/,
use: [{
loader: 'url-loader',
options: {
limit: 50000,
}
}]
}
]
},
devServer: {
contentBase: path.resolve(ROOTPATH, './app'),//为一个目录下的文件提供本地服务器,在这里设置其所在目录
historyApiFallback: true,//跳转将指向index.html
inline: true,//开启自动刷新页面
port: 1234,//设置默认监听端口,如果省略,默认为"8080"
hot: true,//开启热替换
},
plugins: [],
};
webpack.dev.js
const merge = require('webpack-merge');
const common = require('./webpack.common.js');
const webpack = require('webpack');
const path = require('path');
const ROOTPATH = path.resolve(__dirname, '.');
module.exports = merge(common, {
output: {
path: path.resolve(ROOTPATH, 'dist'), //打包的文件夹
filename: '[name].js',
publicPath: ''
},
module: {
rules: [
{
test: /\.css$/,
use: [{
loader: 'style-loader'
}, {
loader: 'css-loader'
}],
},
{
test: /\.scss$/,
use: [{
loader: 'style-loader'
}, {
loader: 'css-loader',
options: {
sourceMap: true
}
}, {
loader: 'sass-loader',
options: {
sourceMap: true,
outputStyle: 'expanded',
sourceMapContents: true
}
}]
},
{
test: /\.less$/,
use: [{
loader: 'style-loader'
}, {
loader: 'css-loader',
options: {
sourceMap: true
}
}, {
loader: 'less-loader',
options: {
sourceMap: true,
outputStyle: 'expanded',
sourceMapContents: true,
}
}]
},
{
test: /index\.html/,
use: [{
loader: 'file-loader',
options: {
name: 'index.[ext]'
}
}]
}
]
},
plugins: [],
});
webpack.pro.js
const path = require('path');
const merge = require('webpack-merge');
const common = require('./webpack.common.js');
const webpack = require('webpack');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const UglifyJsPlugin = webpack.optimize.UglifyJsPlugin;
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CommonsChunkPlugin = webpack.optimize.CommonsChunkPlugin;
const ROOTPATH = path.resolve(__dirname, '.');
module.exports = merge(common, {
output: {
path: path.resolve(ROOTPATH, 'dist'), //打包的文件夹
filename: '[name].[hash].bundle.js',
publicPath: ''
},
module: {
rules: [
{
test: /\.css$/,
use: ExtractTextPlugin.extract({fallback: 'style-loader', use: ['css-loader']}),
},
{
test: /\.scss$/,
use: ExtractTextPlugin.extract({
use: [{
loader: 'css-loader'
}, {
loader: 'sass-loader'
}]
})
},
{
test: /\.less$/,
use: ExtractTextPlugin.extract({
use: [{
loader: "css-loader", options: {importLoaders: 1}
}, {
loader: "postcss-loader"
}, {
loader: 'less-loader'
}]
})
},
]
},
plugins: [
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: JSON.stringify('production')
}
}),
new ExtractTextPlugin({
filename: '[name].[contenthash].css',
allChunks: true, // don't contain embedded styles
}),
//加入js压缩的实例
new UglifyJsPlugin({
mangle: {
mangle: false
},
compress: {
sequences: true,
dead_code: true,
conditionals: true,
booleans: true,
unused: false,
if_return: true,
join_vars: true,
drop_console: false,
warnings: false
},
}),
new HtmlWebpackPlugin({
title: 'xxx局',
filename: path.resolve(ROOTPATH, 'dist/template.html'), // the path to create html
template: path.resolve(ROOTPATH, 'app/public/template.html'), //the path of template html,
minify: false,
// favicon: path.resolve(__dirname, 'app/images/cscec_favicon-2.ico')
}),
new CleanWebpackPlugin(['dist', 'dist.zip', 'dist.rar'], {exclude: ['lib']}),
new CommonsChunkPlugin({
name: 'vendor',
filename: 'vendor.bundle.js',
minChunks: function (module, count) {
// any required modules inside node_modules are extracted to vendor
return (
module.resource &&
/\.js$/.test(module.resource) &&
module.resource.indexOf(
path.join(__dirname, './node_modules')
) === 0
)
}
}),
],
});
创建app/public/template.html
修改package.json
"scripts": {
"build": "set NODE_ENV=production&&webpack -p --config webpack.pro.js",
"start": "webpack-dev-server --devtool eval --progress --colors --config webpack.dev.js "
},
创建postcss.config.js
module.exports = {
plugins: [
require('postcss-smart-import')({ /* ...options */ }),
require('precss')({ /* ...options */ }),
require('autoprefixer')({ /* ...options */ })
]
};
执行npm run build
生成的代码已经进过了分离,压缩,添加了版本号,能够利用缓存提升加载速度
11.在react中使用jsplumb、jquery和jquery-ui。
方法1.ProvidePlugin
自动加载模块,不用再每个组件使用require和import
<!--在webpack.common.js的plugins中添加-->
new webpack.ProvidePlugin({
"$": "jquery",
"jQuery": "jquery",
"window.jQuery": "jquery"
})
此时可以使用全局变量$和jQuery了,可以在任意一个组件中console.log($)
就可以看到一个函数
<!--引入需要jquery的插件-->
import jp from '../../plugins/jsplumb/jsplumb';
import "jquery-ui/ui/widgets/draggable";
接下来就可以使用jquery和jquery的插件了
<!--可以使用$以及使用jquery-ui的draggable方法了-->
$(el).draggable({
helper: "clone",
containment: ".js-layout",//设置拖动的范围
scroll: false,
cursor: "pointer",//设置拖动光标总是在中心
cursorAt: {top: 30, left: 60},
start: function (ev) {
wh = {
w: $(this).width() / 2,
h: $(this).height() / 2
}
},
drag: function (ev) {
offset = $(this).offset();
},
stop: function (ev) {
console.log('鼠标的位置', {top: ev.clientY, left: ev.clientX});
console.log('拖放元素的位置', {top: offset.top, left: offset.left});
let pos = {x: ev.clientX - offset.left - wh.w - 9, y: ev.clientY - offset.top - wh.h - 100 - 8};
movecb(pos)
}
});
方法2.expose-loader
使用expose-loader可以暴露全局变量
module: {
rules: [
{
test: require.resolve('jquery'),
use: [{
loader: 'expose-loader',
options: 'jQuery'
}, {
loader: 'expose-loader',
options: '$'
}]
},
]
},
此时的效果和方法一的一样 webpack文档
使用jquery-ui的方法
使用npm
<!--可以通过npm i下载-->
npm i jquery-ui --save
<!--然后在组件中引入-->
import "jquery-ui/ui/widgets/draggable";
需要一个个引入你需要的方法,也可以做一个总文件进行引入
直接下载
从官网下载jquery-ui,然后放到项目中的一个文件夹下
import "../../plugins/jquery-ui"
这样可以直接引入生效,但是打包的文件会大不少
查看文章
webpack多页应用架构系列(四):老式jQuery插件还不能丢,怎么兼容? Webpack引入jquery及其插件的几种方法
12.开发常用工具
- prop-types
- classnames
附1.依赖
搭建框架
babel基本
-
[babel-cli]
-
babel-core 可以对一些代码直接调用Babel的API进行转码
-
babel-loader 使babel在webpack中支持
-
babel-preset-env 最新的标准版本取代stage0,1,2,3等等
-
babel-preset-react 预设包含了所有的 React 插件
babel其他
-
babel-plugin-react-transform 任意转换的方式去封装 React 组件
-
babel-plugin-transform-object-rest-spread 使对象支持Object rest spread,展开运算符
-
babel-plugin-transform-remove-strict-mode 将"use strict"去掉,看需求可用可不用 官网
-
babel-plugin-transform-runtime 在 Babel 转换过程中,详细的展示引用的相关辅助工具和内置命令,并自动的聚合填充你的代码而不会污染全局
react
react-router
[react-router-dom]
redux
redux-freeze运行时报错机制
redux-thunkredux的异步中间件
redux-logger日志中间件
redux-immutable在不可变数据类型中合并reducer
redux-create-reducer使用action和reducer的关联变的简单
ant-design
babel-plugin-import 用于antd按需加载
loader
[file-loader]
[url-loader]
[image-webpack-loader]
[extract-text-webpack-plugin] 将css单独打包
来源:oschina
链接:https://my.oschina.net/u/4414351/blog/3639083