基於 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}"
來手動進行項目文件的格式化。