基于 Webpack 3 的 React 工程项目脚手架从属于笔者的 Web 前端入门与工程实践,算来已是笔者 React 技术栈脚手架的第四个迭代版本。更多关于 React 或者前端开发相关的资料连接能够参考React 学习与实践资料索引以及 Webpack 学习与资料索引,对于其中浅薄的工程化的思考能够参考 2016-个人前端之路:工具化与工程化。javascript
create-react-boilerplate 是笔者对于平常工做中的基于 React 技术栈与实践的沉淀,dev-config/* 与 package.json 构成了基础的脚手架,支持最新的开发流程与默认的生产环境优化;模板项目包含特性以下:css
技术栈支持:使用 ES6/ES7 语法、使用 React Router V四、容许使用 CSS Modules、SCSS、Less 而且使用 PostCSS 进行自动 Polyfill、支持使用 styled-component 进行 CSS-in-JS 样式控制、使用 Flow 做为静态类型检测工具、使用 Jest 做为默认的测试框架html
开发环境:使用 WebpackDevServer 部署开发服务器、使用 React Hot Loader 进行组件热加载、使用 Babel 进行代码转换、使用 ESLint 进行代码检测、使用 DllPlugin 做为开发环境下公共代码提取工具以优化编译速度前端
生产环境:使用 CommonChunksPlugin 做为生产环境下公共代码提取工具、使用 Prepack & prepack-webpack-plugin 进行代码优化、使用 offline-plugin 添加简单的 PWA 特性加强java
部署方式:支持独立部署(Hash 方式切换路由)、支持服务端部署、支持服务端渲染node
咱们能够直接拷贝该项目来展现部分开发模式或者做为模板项目使用:react
# 下载本项目 git clone https://github.com/wxyyxc1992/create-react-boilerplate # 可使用 yarn install & npm start 直接运行本项目 # 仅保留 dev-config、package.json、src/client.js、src/ssr_server.js mkdir /path/to/your/project # 拷贝必须的启动文件 cp -r dev-config/ /path/to/your/project cp package.json /path/to/your/project cp src/client.js /path/to/your/project/src/ cp src/ssr_server.js /path/to/your/project/src/ # 安装运行依赖 cd /path/to/your/project yarn install / npm install # 启动项目 npm start # 编译为纯客户端部署模式,即单个 HTML 页面 npm run build # 编译为服务端渲染模式(主要区别在于路由支持) npm run build:ssr # 进行依赖升级检查 npm run update # 启动 Storybook npm run storybook
此外本项目中的演示代码还包含了性能优化、设计模式、样式指南、Redux、MobX 等常见的开发模式,在线演示地址:http://wxyyxc1992.github.io/crb/;目前演示代码还处于完善阶段,能够关注代码仓库了解最新更新:webpack
性能优化git
懒加载:github
组件的异步加载:src/case/performance/lazy/loadable
外部依赖脚本(JS / CSS)的异步加载:src/case/performance/lazy/external/*
WebAssembly:WebAssembly 初体验:重构计算模块
简单计数器:src/case/performance/web_assembly/counter
WayOfLife 游戏引擎:src/case/performance/web_assembly/game
设计模式
权限校验:
基于 React-Router-V4 的登陆与权限控制验证:src/case/designpattern/auth
样式指南
Redux
MobX
TODOApp
将来笔者也会同步升级 create-react-boilerplate
命令行工具以快速建立项目;此外本文档仅是对于项目中使用的 Webpack 配置进行说明,详细的 Webpack 学习资料能够参考笔者在 React 与前端工程化实践一书中的 React 初窥与 Webpack 工程实战两章。
create-react-boilerplate 默认的应用配置位于 dev-config/apps.config.js 文件中,该文件也是 dev-config/ 文件夹下惟一与应用业务相关的文件;该文件定义了不一样应用中的须要配置的应用相关信息。create-react-boilerplate 定位为单项目多应用的模板,所以咱们能够在apps
键下配置项目设计的应用入口;在打包时会自动将多个应用并行编译而且提取出全部公共的代码。每一个应用须要提供惟一编号、入口文件地址、模板页面、是否编译等信息;接下来 devServer 则是定义了当前正在开发的应用入口,ssrServer 定义了打包时须要使用的渲染服务器入口,其会在执行 npm run build:ssr
时调用,proxy
与 api
则定义了后端服务器信息,开发者能够根据业务需求自行使用。典型的 apps.config.js 文件配置以下:
module.exports = { //基本的应用配置信息 apps: [ //HelloWorld { id: "pwa", src: "./pwa/client.js", indexPage: defaultIndexPage, compiled: true } ], //开发入口配置 devServer: { appEntrySrc: "./pwa/client.js", //当前待调试的APP的入口文件 port: 3000 //监听的Server端口 }, //用于服务端渲染的Server路径 ssrServer: { serverEntrySrc: "./pwa/ssr_server.js" }, //依赖项配置 proxy: { //后端服务器地址 http://your.backend/ "/api/*": "http://localhost:3001" }, //后端 api 配置,这样配置能够避免将测试服务器端口暴露出去 api: { dev: {}, prod: {} } };
这里还须要说起的是在 *client.js 入口文件中,咱们还须要引入封装以后的渲染方法以支持热加载,其模板为:
// @flow import React from "react"; import App from "./container/App"; import { clientRender } from "../dev-config/tool/render"; //将组件渲染到DOM中 clientRender(<App />, document.getElementById("root"), "./container/App", true);
在 dev-config/webpack/loaders.js 文件中定义了模板所须要的加载器,默认支持 js、jsx、ts、tsx、css、scss、less、json 以及各类资源文件等常见格式。当咱们执行 npm start
命令时,会自动启动dev-config/server/devServer.js 文件中定义的 Webpack 开发服务器,该服务器会使用 dev-config/webpack.config.js 文件进行配置项生成。值得一提的是,WebpackDevServer 中的 contentBase 设置为了 path.join(__dirname, "../../public")
,也就是将 /public 目录做为开发服务器的默认根目录。create-react-boilerplate 默认使用 react-hot-loader 添加 React 热加载支持,其配置包括如下步骤:
开发时应用入口设置:
entry = [ "react-hot-loader/patch", `webpack-dev-server/client?http://0.0.0.0:${appsConfig.devServer.port}`, "webpack/hot/only-dev-server", require("./apps.config.js").devServer.appEntrySrc ];
Babel 配置,默认的 Babel 文件位于 dev-config/tool/.babelrc:
... "plugins": [ "react-hot-loader/babel", ...
React Hot Loader 3 并未实现模块热替换接口,所以咱们还须要重载自定义的渲染方法,参考 dev-config/tool/render.js 文件中的实现:
import React from 'react' import ReactDOM from 'react-dom' import { AppContainer } from 'react-hot-loader' import App from './containers/App' ReactDOM.render( <AppContainer> <App/> </AppContainer>, document.getElementById('root') ); // Hot Module Replacement API if (module.hot) { module.hot.accept('./containers/App', () => { const NextApp = require('./containers/App').default; ReactDOM.render( <AppContainer> <NextApp/> </AppContainer>, document.getElementById('root') ); }); }
create-react-boilerplate 支持 SCSS、CSS Modules 以及 styled-components 这三种样式定义方式,鉴于默认是将全部的 .css 文件按照 CSS Modules 方式载入;所以若是想不使用 CSS Modules 来声明样式,即便不使用 SCSS 语法也须要将样式文件后缀声明为 .scss。
SCSS
// 声明 .Showcase__container { height: 100%; ... // 左侧导航栏 .Showcase__navigator { flex: 240px 0 0; ... } // 右侧展现区域 .Showcase__cases { flex: 80% 1 1; padding: 5px 10px; } } // 引入 import "./Showcase.scss";
styled-components
import styled from "styled-components"; const ShowcaseHeaderContainer = styled.section` padding:1% 2%; margin-bottom:1%; background:white; color:rgba(0, 0, 0, 0.65); border-bottom:1px solid #e9e9e9; `; const ShowcaseHeaderTitle = styled.h1` color:rgba(0, 0, 0, 0.65); `; const ShowcaseHeaderDescription = styled.h2` color:rgba(0, 0, 0, 0.65); `;
CSS Modules
// 正常声明 .tip{ font-size: 20px; } // 使用 import styles from "./Private.css"; ... <span className={styles.tip} />
在开发环境下样式会被 style-loader 之内联样式导入,而生产环境下则会经过 ExtractTextPlugin 抽取为单独的 CSS 文件。
exports.styles = { css: { test: /\.css$/, use: __DEV__ ? ["style-loader", moduleCSSLoader, postCSSLoader] : ExtractTextPlugin.extract({ use: [moduleCSSLoader, postCSSLoader] }) }, scss: { test: /\.(scss|sass)$/, use: __DEV__ ? ["style-loader", "css-loader", postCSSLoader, "sass-loader"] : ExtractTextPlugin.extract({ use: ["css-loader", postCSSLoader, "sass-loader"] }) }, less: { test: /\.(less)$/, use: __DEV__ ? ["style-loader", "css-loader", postCSSLoader, "less-loader"] : ExtractTextPlugin.extract({ use: ["css-loader", postCSSLoader, "less-loader"] }) } };
create-react-boilerplate 使用了 CommonsChunkPlugin 进行代码分割,默认在 dev-config/webpack/plugins.js 文件中定义了对于 node_modules 中依赖文件的自动抽取:
new webpack.optimize.CommonsChunkPlugin({ name: "vendor", filename: "vendor.bundle.js", minChunks: ({ resource }) => resource && resource.indexOf("node_modules") >= 0 && resource.match(/\.(js|less|scss)$/) })
该插件会自动生成 vendor.bundle.js 文件,咱们须要在应用入口文件以前引用它;开发者也能够自定义 CommonsChunkPlugin 插件以自定义须要提取的公共代码。
随着项目复杂度与体量的增长,咱们发现初始化编译与增量编译的速度都有所降低,为了提高构建性能首先咱们要作的就是保持 Webpack 版本的更新速度;此外,create-react-boilerplate 还默认启动了 DllPlugin 在开发状态下将全部的依赖提取为 dll 文件以提升增量编译的速度。由于考虑到灵活性,即随时有可能增减依赖的状况,create-react-boilerplate 目前设置的是每次使用 npm start
的时候都会从新生成 dll 文件;若是是已经稳定的项目能够考虑仅生成一次依赖。
const path = require("path"); const pkg = require("../package.json"); const webpack = require("webpack"); let dllConfig = { name: "vendor", entry: Object.keys(pkg.dependencies), output: { path: path.resolve(__dirname, "../public/dll"), filename: "vendor.bundle.js", library: "vendor_[hash]" }, plugins: [ new webpack.DllPlugin({ name: "vendor_[hash]", path: path.resolve(__dirname, "../public/dll/manifest.json") }) ] }; module.exports = dllConfig; // 在 public/index.html 文件中须要引入该依赖 // index.html <script src="dll/vendor.bundle.js"></script>
create-react-boilerplate 中也内置了其余的编译以后的代码性能优化插件,首先是利用 Webpack 3 的 Scope Hositing 特性来优化生成的模块;这一点须要使用 ModuleConcatenationPlugin 插件。此外,还使用了 PrepackWebpackPlugin 对于打包生成的文件进行过滤与重构;不过须要注意的是 PrepackWebpackPlugin 会较大地下降编译速度,所以也是能够根据实际的项目状况选用。
// 使用 Scope Hositing 特性 new webpack.optimize.ModuleConcatenationPlugin(), // 使用 Prepack 优化包体大小 // 暂时存在 Bug,等待修复 // 使用前 21 - 425 // 使用后 21 - 433 new PrepackWebpackPlugin({ mathRandomSeed: "0" }),
create-react-boilerplate 中只是简单地使用了 Offline Plugin,其配置以下:
// webpack.config.js example var OfflinePlugin = require('offline-plugin'); module.exports = { // ... plugins: [ // ... other plugins // it's always better if OfflinePlugin is the last plugin added new OfflinePlugin() ] // ... } // render.js require('offline-plugin/runtime').install();
观察网络面板中的资源请求状况,咱们能够看到脚本等已经被缓存在了本地:
在 create-react-boilerplate 中使用了 react-loadable 进行组件异步分割与加载,参考 src/case/performance/lazy/Lazy.js 文件了解完整实现。咱们首先经过 Loadable 封装须要异步加载的组件:
export const LoadableLazyComponent = Loadable({ loader: () => import("./LazyComponent"), loading: LoadingPlaceholder, delay: 200 // serverSideRequirePath: path.join(__dirname, "./LazyComponent"), // webpackRequireWeakId: () => require.resolveWeak("./LazyComponent") });
而后引入封装组件 import { LoadableLazyComponent } from "./loadable/LoadableLazyComponent";
如常使用便可。
create-react-boilerplate 目前展现了基础的基于 React Router V4 的服务端渲染支持:
// AppContainer.js const Router = __SSR__ ? BrowserRouter : HashRouter; // ssrServer.js //处理全部的请求地址 app.get('/*', function(req, res) { try { // 判断页面是否匹配 const match = routes.reduce((acc, route) => { return matchPath(req.url, { path: route, exact: true }) || acc; }, false); // 若是待寻找页面不存在 // 仅当访问 404 界面时,提示不存在 if (match) { res.status(404).send(renderToString(<NoMatch location={req.url} />)); return; } // 存放渲染以后的 Context 数据 let context = {}; // 将组件渲染为 HTML let markup = renderToString( <StaticRouter context={context} location={req.url}> <App serverSideMessage={'Hello World By Server Side Rendering'} /> </StaticRouter> ); // 判断是否存在转发 if (context.url) { res.writeHead(301, { Location: context.url }); res.end(); } else { res .status(200) .send( renderHTML( markup, { key: 'value' }, ['/static/vendor.bundle.js', '/static/index.bundle.js'], ['/static/index.css'] ) ); res.end(); } } catch (e) { console.error(e); res.status(500).send(e.message); } });
若是须要进行数据预抓取,能够考虑将数据挂载到页面上进行传递。
详细的 JavaScript 编程样式指南已经迁移到了 Web 项目开发风格指南与 JavaScript 编程样式指南,涵盖了基本原则阐述、代码风格、代码格式化与语法检测、项目架构等几个部分。不过本部分建议是相似于 Create React APP 配置提交时自动进行格式化,首先须要安装以下依赖:
npm install --save husky lint-staged prettier // or yarn add husky lint-staged prettier
而后在 package.json 中添加 Hook:
"scripts": { "precommit": "lint-staged", ...
同时添加 lint-staged 配置:
"dependencies": { // ... }, + "lint-staged": { + "{src,stories}/**/*.{js,jsx,json,css}": [ + "prettier --single-quote --write", + "git add" + ] + }, "scripts": {
这样当咱们提交代码时就会自动使用 Prettier 优化代码,不过须要注意的是这种配置仅做用于根目录下;若是某个仓库中包含了多个应用配置,那么咱们还须要在根目录下单独配置脚本。咱们也可使用 ./node_modules/.bin/prettier --single-quote --write "src/**/*.{js,jsx}"
来手动进行项目文件的格式化。