关于create-react-app:craco是如何实现对非moduleless文件开启css-modules的
创立了两个loader,别离是lessRule和lessModuleRule,这两个loader外面都开启了css modules。 lessRule的css modules配置 lessModuleRule的css modules配置
创立了两个loader,别离是lessRule和lessModuleRule,这两个loader外面都开启了css modules。 lessRule的css modules配置 lessModuleRule的css modules配置
如何配置less?// craco.config.jsconst CracoLessPlugin = require('craco-less');module.exports = { plugins: [ { plugin: CracoLessPlugin, options: { cssLoaderOptions: { modules: { localIdentName: '[local]_[hash:base64:5]' }, }, }, }, ],};如何配置门路别名(alias)// craco.config.jsconst path = require('path');module.exports = { webpack: { alias: { '@': path.resolve(__dirname, 'src'), }, },};
背景上篇文章《前端我的项目本地调试计划》中讲到开发chrome拓展插件帮忙实现Cookie复制,从而实现本地我的项目调试。但插件采纳的是原生JS开发的,本文来探讨如何在我的项目中引入React? 我的项目实际首先,执行上面命令初始化我的项目 create-react-app chrome-extension --template typescript创立的我的项目构造如下: 将红色圈出的文件删除,调整的构造如下: 批改pubic文件夹中manifest.json配置文件,增加须要应用的图标、权限,整体配置如下: { "manifest_version": 2, // 为2时默认开启内容安全策略 "name": "debug", "description": "前端我的项目调试工具", "version": "1.0.0", "icons": { "16": "/images/icon16.png", "32": "/images/icon32.png", "48": "/images/icon48.png", "128": "/images/icon128.png" }, "permissions": [ "cookies", "tabs", "http://*/*", "https://*/*", "storage" ], "browser_action": { "default_icon": { "16": "/images/icon16.png", "32": "/images/icon32.png", "48": "/images/icon48.png", "128": "/images/icon128.png" }, "default_popup": "index.html" // 弹窗页面 }, "content_security_policy": "script-src 'self'; object-src 'self'" // 内容安全策略(CSP)}删除index.html中文件的援用,调整后如下: <!DOCTYPE html><html lang="en"> <head> <meta charset="utf-8" /> </head> <body> <noscript>You need to enable JavaScript to run this app.</noscript> <div id="root"></div> </body></html>在public中增加images目录寄存图标: ...
环境要求:有装置node和npm,没有装置的话,能够参考以下链接 https://segmentfault.com/a/1190000038536950接下来,生成一个react 我的项目: 全局装置create-react-app npm install create-react-app -g抉择我的项目目录,运行以下命令, projectname 是我的项目的名字 creacte-react-app projectname 进入到我的项目目录,运行npm run start在浏览器输出http://localhost:3000/,呈现以下界面:至此,曾经ok了
create-react-app搭建react我的项目后,是不反对润饰器语法的。react-scripts库曾经提供了打包cli命令以及惯例的构建配置,如果须要应用一些应用时个性,比方润饰器等,须要另外注入 正好,官网提供react-app-rewired库,可能帮忙咱们注入自定义构建配置 首先,装置react-app-rewired yarn add react-app-rewired -S将react-scripts全副替换成react-app-rewired: "scripts": { "start": "react-app-rewired start", "build": "react-app-rewired build", "test": "react-app-rewired test", "eject": "react-app-rewired eject" },而后在工程的根目录下新建config-overrides.js配置文本,加上润饰器配置脚本代码: const { injectBabelPlugin } = require('react-app-rewired');module.exports = function override(config, env) { config = injectBabelPlugin([ "@babel/plugin-proposal-decorators", { "legacy": true } ], config) return config;}执行npm run start后提醒咱们应用customize-cra自定义配置 进去customize-cra官网理解一下如何应用 照着下面配置就能够让我的项目反对润饰器语法了 const { override, addDecoratorsLegacy, } = require('customize-cra');module.exports = override( addDecoratorsLegacy())
前言如今,对于现今前端热门的三大框架Vue,Angular,React,对于web开发者来说,早已不是什么陌生的词 尽管三者实现业务最终的目的都能达成一致,但是各有特色,其中任何一框架,个人觉得,都博大精深,可圈可点,要学习的内容有很多,我也仅仅是浅尝辄止而已. 有时候,因为工作项目的需要,自己在切换各个技术栈的时候,只要一段时间没有用,就有些陌生,说到底不得不承认自己功力不够 本文并不是什么教程,只是作为自己学习过程中的一些总结和思考,一起学习,共同成长~ 正文从这里开始~ React是什么?用于构建用户界面的javascript库,MVC架构中的V层 声明式编程(想要实现什么目的,应该做什么,但是不指定具体怎么做,如下代码所示)面向数据编程,只要把数据构建好了就可以了的,react会自动的帮你去构建网站,把数据可以理解为图纸,图纸做好了之后,React会自动的结合这张图纸帮助你去构建这个大厦,去构建整个页面的DOM 数据是什么,页面就显示什么,这种声明式的开发帮助我们节约掉大量的DOM操作,这是React编程带来的一个优势) /**** 需求:编写一个函数,处理传入包含大写字符串的数组,返回包含相同小写字符串的数组* 声明式编程实现toLowerCase* 输入数组的元素传递给map函数,然后返回包含小写值的新数组*/let toLowerCase = arr => arr.map(function(item) {return item.toLowerCase();})let aToCasesA = ['SUIBICHUANJI','ITCLANCODER', 'ZHONGGUO', 'BEIJING', 'AGE'];let aToCasesB = toLowerCase(aToCasesA);console.log(aToCasesA); // ["SUIBICHUANJI", "ITCLANCODER", "ZHONGGUO", "BEIJING", "AGE"]console.log(aToCasesB); // ["suibichuanji", "itclancoder", "zhongguo", "beijing", "age"]命令式编程(类似jQuery操作DOM,创建一个页面,你要一点点的告诉DOM怎么去挂载,要怎么去做,JQ,原生也好都是命令式编程,都是在做DOM操作,获取元素,绑定元素,执行操作)/** 命令式编程:按照顺序一步一步的实现* 首先,创建一个空数组用于保存结果,然后遍历输入数组的所有元素,将每项元素的小写值存入空数组中,然后返回结果数组*/var aToCasesA = ['SUIBICHUANJI', 'ITCLANCODER', 'ZHONGGUO', 'BEIJING', 'AGE'];function toLowerCase(arr) {var res = [];for(var i = 0; i < arr.length; i++) {res.push(arr[i].toLowerCase());}return res;}var aToCasesB = toLowerCase(aToCasesA);console.log(aToCasesA); // ["SUIBICHUANJI", "ITCLANCODER", "ZHONGGUO", "BEIJING", "AGE"]console.log(aToCasesB); // ["suibichuanji", "itclancoder", "zhongguo", "beijing", "age"]函数式编程:写的都是一些函数,带来的好处,是维护起来比较容易,当一个函数比较大的时候,可以进行拆分,每一个函数各司其职,便于前端自动化测试(数组中的一些map,reduce,find等方法的应用就是函数式编程)视图层框架(在大型项目中,光用React是不行的,还得配合一些数据层的框架帮助我们解决一些组件之间的父子组件传值的问题,React把自己定义成一个视图层的框架,并不是什么问题都能解决,只是帮助你解决数据和页面渲染的问题,至于组件之间怎么传值,交给其他组件来做.在小型项目中,可以借助React中的父子组件传值就可以,但是在大型项目里,单单来使用React是不够的,比如说:flux,redux,mobox这样的数据层框架),React并不是一个完整的框架,所以它学习的成本也就相对高些的. ...
使用create-react-app脚手架创建出来的react项目,他的配置项是隐藏的,如果想要修改原始的配置项,需要npm run eject,但是这个操作是不可逆的,一般情况下我们不会去直接修改他的原始配置项。 那么如果我想在用create-react-app脚手架创建的项目中使用antd design 官方推荐的按需加载要怎么添加自己的配置项呢?此时我们可以用 react-app-rewired 来实现。 第一步:安装antd按需加载的插件babel-plugin-import npm install babel-plugin-import --save-dev第二步:安装react-app-rewired $ npm install react-app-rewired --save-devreact-app-rewired是一个再配置的工具。安装完之后在根目录新建一个config-overrides.js的文件,在这个配置文件中新增加自己的自定义配置,可以实现在不eject的情况下自定义配置。 第三步:安装customize-cra npm install customize-cra --save-dev使用customize-cra要确保先安装了react-app-rewired。接下来就可以来配置按需加载了。首先在项目的根目录下新建一个config-overrides.js文件,接下来在这个文件中写我们自己的配置 const { override, fixBabelImports } = require('customize-cra');module.exports = override( fixBabelImports('import', { libraryName: 'antd', libraryDirectory: 'es', style: 'css' }))之后在组件中测试 import React, { Component } from 'react';import { Button } from 'antd';class Test extends Component { render() { return ( <div> <Button type="primary">点击</Button> </div> ) }}export default Test;这样就可以实现按需加载antd的组件,并且无需手动引入样式了。 ...
通过各种百度, 整理出一份配置步骤,望有助于需要的朋友 当前create-react-app v 3.0.1 //基本步骤create-react-app test cd test npm run eject //调整webpack.config.js的几个地方 1, entry 由数组修改成对象形式 2, output的修改 修改成 3, HtmlWebpackPlugin index默认的修改成如 图: 自己添加的一份demo 至此, 添加了一份demo的配置就完成了, 可以访问 首页 localhost:3000/index.htmldemo页: localhost:3000/demo.html无需像网上及webpack文档说的,dev下还需要修改hot配置页面 另外,如果有需求像访问 localhost:3000/demo/index.html 这样的地方,只需要针对上面的步骤修改一个地方即可 有问题欢迎加群 2751786 @陌路
根据自己常用的一些配置和需求,制定了一个项目模板基于 creat-react-app v2.x 搭建的项目模版以后会持续更新,不过频率不会很高项目名地址 gitee 技术栈 reactreact-routermobxless(css module)less-modules使用babel-plugin-react-css-modules.这里需要注意一下,babel-plugin-react-css-modules的配置项:generateScopedName,其默认值是[path]___[name]__[local]___[hash:base64:5];而对应的css-loader配置项localIdentName的默认值是[hash:base64:5].导致元素的类名和编译出来的 css 类名不匹配. 当前配置的css-module是如果直接以.less结尾则使用css-module,如果以global.less则使用原始的less. 打包后的资源路径默认打包后,index.html文件中的资源路径都是以/开头的,这可能会导致引用错误.解决方法: /config/paths.js- envPublicUrl || (publicUrl ? url.parse(publicUrl).pathname : '/');+ envPublicUrl || (publicUrl ? url.parse(publicUrl).pathname : './');proxy新版本的脚手架无法在package.json中使用对象形式的proxy.需要安装http-proxy-middleware插件.然后配置proxy cd my-projectyarn add http-proxy-middleware -Dtouch setupProxy.js注意/setupProxy.js这个文件的路径在/config/paths.js中设置.按照插件说明配置完后,重启即可自动代理
文件结构分析 env.js // Load environment variables from .env* files. Suppress warnings using silent// if this file is missing. dotenv will never modify any environment variables// that have already been set. Variable expansion is supported in .env files.// https://github.com/motdotla/dotenv// https://github.com/motdotla/dotenv-expand//注释其实写的很清楚,用dotenv和dotenv-expand工具读取所有.env*文件,获取变量dotenvFiles.forEach(dotenvFile => { if (fs.existsSync(dotenvFile)) { require('dotenv-expand')( require('dotenv').config({ path: dotenvFile, }) ); }});//最后赋值给process.env对象// Stringify all values so we can feed into Webpack DefinePlugin const stringified = { 'process.env': Object.keys(raw).reduce((env, key) => { env[key] = JSON.stringify(raw[key]); return env; }, {}), };modules.js其实就两个函数,为了读取jsconfig.json或者tsconfig.json文件中的配置,两个文件不能同时存在. ...
菜鸟学习react中,配置文件有点乱,研究一波。 yarn eject之后,文件目录相比之前只是多了config和scripts两个文件夹,package.json多了很多配置项,所以本文主要解决config和package.json两个部分. 一.config文件夹下,还有一个jest文件夹是jest测试文件,cssTransform.js和fileTransform.js对应package.json中的 "transform": { "^.+\\.(js|jsx|ts|tsx)$": "<rootDir>/node_modules/babel-jest", "^.+\\.css$": "<rootDir>/config/jest/cssTransform.js", "^(?!.*\\.(js|jsx|ts|tsx|css|json)$)": "<rootDir>/config/jest/fileTransform.js" },二.重点分析,从package.json命令行入手, "scripts": { "start": "node scripts/start.js", "build": "node scripts/build.js", "test": "node scripts/test.js" },看命令就知道scripts下的三个文件,分别对应开发,打包和测试三个环境的入口。 config/path.js里面路径变量 const resolveApp = relativePath => path.resolve(appDirectory, relativePath);//解析对应文件的绝对路径const getPublicUrl = appPackageJson => envPublicUrl || require(appPackageJson).homepage;//赋值下面变量publicUrl,从env.js或者package.json中获取homepagefunction getServedPath(appPackageJson) { const publicUrl = getPublicUrl(appPackageJson); const servedUrl = envPublicUrl || (publicUrl ? url.parse(publicUrl).pathname : '/'); return ensureSlash(servedUrl, true);}//服务域名 ---------- dotenv: resolveApp('.env'), appPath: resolveApp('.'), appBuild: resolveApp('build'), appPublic: resolveApp('public'), appHtml: resolveApp('public/index.html'), appIndexJs: resolveModule(resolveApp, 'src/index'), appPackageJson: resolveApp('package.json'), appSrc: resolveApp('src'), appTsConfig: resolveApp('tsconfig.json'), appJsConfig: resolveApp('jsconfig.json'), yarnLockFile: resolveApp('yarn.lock'), testsSetup: resolveModule(resolveApp, 'src/setupTests'), proxySetup: resolveApp('src/setupProxy.js'), appNodeModules: resolveApp('node_modules'), publicUrl: getPublicUrl(resolveApp('package.json')), servedPath: getServedPath(resolveApp('package.json')),1.start.js 其实就是开发环境devserver的配置和启动 ...
描述本文讲解使用create-react-app创建的项目,如何部署GitHub Pages,以及这部署到过程中遇到到坑。 创建项目使用官网方式创建项目。 npx create-react-app my-appcd my-appnpm install弹出配置 npm run ejectGithub Pages部署讲解把项目部署成github pages,在github上点开项目到设置,翻到Github Pages设置处,可以看到Github Pages只能使用master、gh-pages分支或者master下面的docs文件夹。我们这里使用的是gh-pages分支的方式来创建。 安装gh-pagesnpm install gh-pages --save-dev通过gh-pages中间件可以把build文件下到文件推送到github,并且创建gh-pages branch。 修改package.json增加homepage "name": "react_demo", "version": "1.1.0", "private": true, "homepage": "./", //加上这一句注意:homepage不要设置成github page上生成的那个链接路径,比如https://username.github.io/react_demo/。如果设置成上面的连接,build打的包会这所有路径前面加上react_demo。比如index.html文件中对同等目录下的文件引用应该是src='./index.css',结果会变成src='./react_demo/index.css',这样部署后肯定无法访问,所有资源都找不到。增加npm scripts命令,推送gh-pages "scripts": { ... + "deploy": "gh-pages -d build" //加上这一句}推送项目GitHub Pages只是部署项目,react代码直接放上去是识别不了的,所以部署的是打包编译后到代码。 npm run build编译后就可以推送了,执行上面配置的命令。 npm run deploy这时github上项目就多出了一个gh-pages的branch,在设置中Github Pages处选择gh-pages分支保存,部署完成。点击生成的连接,查看是否部署成功。 2019-05-25
描述创建步骤和官网一直,大家可以查看官网,下面简单列举下.创建项目npx create-react-app my-appcd my-app/eject出配置文件npm run eject或者yarn eject安装lessless和less-loader都要安装。less是支持less语法的,less-loader是webpack使用来编译less的。npm install less less-loader –save配置webpack.config.js修改config/webpack.config.js新增less配置变量const cssRegex = /.css$/;const cssModuleRegex = /.module.css$/;const sassRegex = /.(scss|sass)$/;const sassModuleRegex = /.module.(scss|sass)$/;const lessRegex = /.less$/; // 新增less配置const lessModuleRegex = /.module.less$/; // 新增less配置,这个其实不配置也行增加module下面rule规则,可以copy cssRegex或者sassRegex的配置。{ test: sassModuleRegex, use: getStyleLoaders({ importLoaders: 2, sourceMap: isEnvProduction && shouldUseSourceMap, modules: true, getLocalIdent: getCSSModuleLocalIdent }, “sass-loader” )}, { test: lessRegex, exclude: lessModuleRegex, use: getStyleLoaders({ importLoaders: 1,// 值是1 modules: true, // 增加这个可以通过模块方式来访问css sourceMap: isEnvProduction && shouldUseSourceMap }, “less-loader” ), sideEffects: true}, // 这个测试删了也不影响{ test: lessModuleRegex, use: getStyleLoaders({ importLoaders: 1, sourceMap: isEnvProduction && shouldUseSourceMap, modules: true, getLocalIdent: getCSSModuleLocalIdent }, “less-loader” )},// “file” loader makes sure those assets get served by WebpackDevServer.需要注意一下几个地方:1.lessRegex中importLoaders的值为1当然这个是2也能使用,但是它的值是根据lessRegex变量后面正则中匹配的loader数来决定的,比如const cssRegex = /.css$/只是处理css一种类型的文件,那应该就是1;const sassRegex = /.(scss|sass)$/;对应的是scss和sass两种类型,那就应该是2.2.lessRegex的use中增加modules配置modules可以不设置,不设置的话,less只能通过字符串名的方式使用,比如定义了一个.title,引用时import ‘./index.less’,使用时:<div className=“title”></div>如果需要通过模块的方式调用,则需要把modules设置成true,就可以通过styles.title方式使用了。import styles from ‘./index.less’,使用<div className={styles.title}></div>第二种配置方式可以不增加新的变量,把css的配置包含lessconst cssRegex = /.(css|less)$/; //增加lessconst cssModuleRegex = /.module.(css|less)$/;{ test: cssRegex, exclude: cssModuleRegex, use: getStyleLoaders({ importLoaders: 2,// 改成2 modules: true,//使用模块方式访问样式 sourceMap: isEnvProduction && shouldUseSourceMap }, “less-loader” //增加loader ), // Don’t consider CSS imports dead code even if the // containing package claims to have no side effects. // Remove this when webpack adds a warning or an error for this. // See https://github.com/webpack/webpack/issues/6571 sideEffects: true}解决了你的问题,请点赞和收藏给点鼓励,有问题请留言。 ...
前言本文是基于react ssr的入门教程,在实际项目中使用还需要做更多的配置和优化,比较适合第一次尝试react ssr的小伙伴们。技术涉及到 koa2 + react,案例使用create-react-app创建SSR 介绍Server Slide Rendering,缩写为 ssr 即服务器端渲染,这个要从SEO说起,目前react单页应用HTML代码是下面这样的<!DOCTYPE html><html lang=“en”> <head> <meta charset=“utf-8” /> <link rel=“shortcut icon” href=“favicon.ico” /> <meta name=“viewport” content=“width=device-width, initial-scale=1, shrink-to-fit=no”/> <meta name=“theme-color” content="#000000" /> <title>React App</title> </head> <body> <noscript>You need to enable JavaScript to run this app.</noscript> <div id=“root”></div> <script src="/js/main.js"></script> </body></html>如果main.js 加载比较慢,会出现白屏一闪的现象。传统的搜索引擎爬虫因为不能抓取JS生成后的内容,遇到单页web项目,抓取到的内容啥也没有。在SEO上会吃很多亏,很难排搜索引擎到前面去。React SSR(react服务器渲染)正好解决了这2个问题。React SSR介绍这里通过一个例子来带大家入坑!先使用create-react-app创建一个react项目。因为要修改webpack,这里我们使用react-app-rewired启动项目。根目录创建一个server目录存放服务端代码,服务端代码我们这里使用koa2。目录结构如下:这里先来看看react ssr是怎么工作的。这个业务流程图比较清晰了,服务端只生成HTML代码,实际上前端会生成一份main.js提供给服务端的HTML使用。这就是react ssr的工作流程。有了这个图会更好的理解,如果这个业务没理解清楚,后面的估计很难理解。react提供的SSR方法有两个renderToString 和 renderToStaticMarkup,区别如下:renderToString 方法渲染的时候带有 data-reactid 属性. 在浏览器访问页面的时候,main.js能识别到HTML的内容,不会执行React.createElement二次创建DOM。renderToStaticMarkup 则没有 data-reactid 属性,页面看上去干净点。在浏览器访问页面的时候,main.js不能识别到HTML内容,会执行main.js里面的React.createElement方法重新创建DOM。实现流程好了,我们都知道原理了,可以开始coding了,目录结构如下:create-react-app 的demo我没动过,直接用这个做案例了,前端项目基本上就没改了,等会儿我们服务器端要使用这个模块。代码如下:import “./App.css”;import React, { Component } from “react”;import logo from “./logo.svg”;class App extends Component { componentDidMount() { console.log(‘哈哈哈~ 服务器渲染成功了!’); } render() { return ( <div className=“App”> <header className=“App-header”> <img src={logo} className=“App-logo” alt=“logo” /> <p> Edit <code>src/App.js</code> and save to reload. </p> <a className=“App-link” href=“https://reactjs.org” target="_blank" rel=“noopener noreferrer” > Learn React </a> </header> </div> ); }}export default App;在项目中新建server目录,用于存放服务端代码。为了简化,我这里只有2个文件,项目中我们用的ES6,所以还要配置下.babelrc.babelrc 配置,因为要使用到ES6{ “presets”: [ “env”, “react” ], “plugins”: [ “transform-decorators-legacy”, “transform-runtime”, “react-hot-loader/babel”, “add-module-exports”, “transform-object-rest-spread”, “transform-class-properties”, [ “import”, { “libraryName”: “antd”, “style”: true } ] ]}index.js 项目入口做一些预处理,使用asset-require-hook过滤掉一些类似 import logo from “./logo.svg”; 这样的资源代码。因为我们服务端只需要纯的HTML代码,不过滤掉会报错。这里的name,我们是去掉了hash值的require(“asset-require-hook”)({ extensions: [“svg”, “css”, “less”, “jpg”, “png”, “gif”], name: ‘/static/media/[name].[ext]’});require(“babel-core/register”)();require(“babel-polyfill”);require("./app");public/index.html html模版代码要做个调整,{{root}} 这个可以是任何可以替换的字符串,等下服务端会替换这段字符串。<!DOCTYPE html><html lang=“en”> <head> <meta charset=“utf-8” /> <link rel=“shortcut icon” href="%PUBLIC_URL%/favicon.ico" /> <meta name=“viewport” content=“width=device-width, initial-scale=1, shrink-to-fit=no”/> <meta name=“theme-color” content="#000000" /> <link rel=“manifest” href="%PUBLIC_URL%/manifest.json" /> <title>React App</title> </head> <body> <noscript>You need to enable JavaScript to run this app.</noscript> <div id=“root”>{{root}}</div> </body></html>app.js 服务端渲染的主要代码,加载App.js,使用renderToString 生成html代码,去替换掉 index.html 中的 {{root}} 部分import App from ‘../src/App’;import Koa from ‘koa’;import React from ‘react’;import Router from ‘koa-router’;import fs from ‘fs’;import koaStatic from ‘koa-static’;import path from ‘path’;import { renderToString } from ‘react-dom/server’;// 配置文件const config = { port: 3030};// 实例化 koaconst app = new Koa();// 静态资源app.use( koaStatic(path.join(__dirname, ‘../build’), { maxage: 365 * 24 * 60 * 1000, index: ‘root’ // 这里配置不要写成’index’就可以了,因为在访问localhost:3030时,不能让服务默认去加载index.html文件,这里很容易掉进坑。 }));// 设置路由app.use( new Router() .get(’*’, async (ctx, next) => { ctx.response.type = ‘html’; //指定content type let shtml = ‘’; await new Promise((resolve, reject) => { fs.readFile(path.join(__dirname, ‘../build/index.html’), ‘utfa8’, function(err, data) { if (err) { reject(); return console.log(err); } shtml = data; resolve(); }); }); // 替换掉 {{root}} 为我们生成后的HTML ctx.response.body = shtml.replace(’{{root}}’, renderToString(<App />)); }) .routes());app.listen(config.port, function() { console.log(‘服务器启动,监听 port: ’ + config.port + ’ running~’);});config-overrides.js 因为我们用的是create-react-app,这里使用react-app-rewired去改下webpack的配置。因为执行npm run build的时候会自动给资源加了hash值,而这个hash值,我们在asset-require-hook的时候去掉了hash值,配置里面需要改下,不然会出现图片不显示的问题,这里也是一个坑,要注意下。module.exports = { webpack: function(config, env) { // …add your webpack config // console.log(JSON.stringify(config)); // 去掉hash值,解决asset-require-hook资源问题 config.module.rules.forEach(d => { d.oneOf && d.oneOf.forEach(e => { if (e && e.options && e.options.name) { e.options.name = e.options.name.replace(’[hash:8].’, ‘’); } }); }); return config; }};好了,所有的代码就这些了,是不是很简单了?我们koa2读取的静态资源是 build目录下面的。先执行npm run build打包项目,再执行node ./server 启动服务端项目。看下http://localhost:3030页面的HTML代码检查下:没有{{root}}了,服务器渲染成功!总结相信这篇文章是最简单的react服务器渲染案例了,这里交出github地址,如果学会了,记得给个starhttps://github.com/mtsee/reac… ...
早些时候CRA(create-react-app)升级到2.0.3的时候, react-app-rewired没有跟着升级, 导致项目无法启动, 于是乎直接eject 开始改造项目.查看版本> create-react-app –version2.0.3创建项目create-react-app my-projectcd my-projectyarn eject # 输入 y目前为止项目目录结构, 忽略node_modules这个黑洞├── README.md├── config│ ├── env.js│ ├── jest│ │ ├── cssTransform.js│ │ └── fileTransform.js│ ├── paths.js│ ├── webpack.config.js│ └── webpackDevServer.config.js├── package.json├── public│ ├── favicon.ico│ ├── index.html│ └── manifest.json├── scripts│ ├── build.js│ ├── start.js│ └── test.js├── src│ ├── App.css│ ├── App.js│ ├── App.test.js│ ├── index.css│ ├── index.js│ ├── logo.svg│ └── serviceWorker.js└── yarn.lock安装依赖yarn add antdyarn add babel-plugin-import less less-loader @babel/plugin-proposal-decorators -DCRA eject之后package.json里面没有区分devDependencies 和 dependencies, 但是不影响使用因为antd是使用的less, CRA默认不支持, 所以需要改下默认的webpack配置, config/webpack.config.js首先修改babel配置个人习惯使用babelrc, 所以把babel-loader options中babelrc的值改为true, 增加.babelrc文件{ “presets”: [ “react-app” ], “plugins”: [ [ “import”, { “libraryName”: “antd”, “libraryDirectory”: “lib”, “style”: true }, “ant” ], [ “@babel/plugin-proposal-decorators”, // 启用装饰器 { “legacy”: true } ] ]}参照默认的sass配置, 增加less配置const sassRegex = /.(scss|sass)$/;const sassModuleRegex = /.module.(scss|sass)$/;const lessRegex = /.less$/;const lessModuleRegex = /.module.less$/;在module>rules中添加规则// sass rule//… { test: lessRegex, exclude: lessModuleRegex, use: getStyleLoaders( { importLoaders: 2, sourceMap: isEnvProduction && shouldUseSourceMap }, ’less-loader’, { javascriptEnabled: true } ), sideEffects: true }, { test: lessModuleRegex, use: getStyleLoaders( { importLoaders: 2, sourceMap: isEnvProduction && shouldUseSourceMap, modules: true, getLocalIdent: getCSSModuleLocalIdent }, ’less-loader’, { javascriptEnabled: true } ) }// file loader至此基本项目虽然已经基本完成, 但是如果你是使用less版本比较高, 项目是无法运行的参考issue需要改造getStyleLoaders函数, 增加第三个参数otherConfig, 就是上面代码中的 javascriptEnabled: trueconst getStyleLoaders = (cssOptions, preProcessor, otherConfig) => { const loaders = [ isEnvDevelopment && require.resolve(‘style-loader’), isEnvProduction && { loader: MiniCssExtractPlugin.loader, options: Object.assign({}, shouldUseRelativeAssetPaths ? { publicPath: ‘../../’ } : undefined) }, { loader: require.resolve(‘css-loader’), options: cssOptions }, { loader: require.resolve(‘postcss-loader’), options: { ident: ‘postcss’, plugins: () => [ require(‘postcss-flexbugs-fixes’), require(‘postcss-preset-env’)({ autoprefixer: { flexbox: ’no-2009’ }, stage: 3 }) ], sourceMap: isEnvProduction && shouldUseSourceMap } } ].filter(Boolean); if (preProcessor) { loaders.push({ loader: require.resolve(preProcessor), options: { sourceMap: isEnvProduction && shouldUseSourceMap, …otherConfig } }); } return loaders; };这样修改之后, 自定义主题modifyVars也可以写在otherConfig中, 一举两得, 不多赘述.至此项目???? ...
更新时间:2019-01-11版本信息:CRA v2.1.1 + Webpack v4.19.1 + react-app-rewired v1.6.2一、前言为什么要进行多页面配置在使用 React 进行开发的过程中,我们通常会使用 creat-react-app 脚手架命令来搭建项目,避免要自己配置 webpack,提高我们的开发效率。但是使用 creat-react-app 搭建的项目是单页面应用,如果我们是做中后台管理页面或 SPA,这样是满足要求的,但如果项目有多入口的需求,就需要我们进行一些配置方面的修改。一般现在有两种方式将脚手架搭建的项目修改为多入口编译:执行 npm eject 命令,弹出配置文件,进行自定义配置。请参见:React-CRA 多页面配置(npm eject)。使用 react-app-rewired 修改脚手架配置,在项目中安装 react-app-rewired 后,可以通过创建一个 config-overrides.js 文件来对 webpack 配置进行扩展。本文对第 2 种方法给出具体配置方案,第 1 种方案详见:React-CRA 多页面配置(npm eject)。在阅读本文之前,可以先了解一下第 1 种方案,有助于更好的理解本文内容。webpack 基础本文对 React 多页面应用配置的探讨是基于使用 create-react-app 脚手架命令构建的项目,并不是 webpack 多页面配置教程。但上述两种方案都应该具有一定的 webpack 的基础,实际上当你决定要改造或增强项目的构建打包配置的时候,你应该先对 webpack 进行一定的了解。如果你之前使用 webpack v3.x 版本,这里附上 webpack v4.0.0 更新日志 以及 webpack4升级完全指南。方案说明通过使用 react-app-rewired 可以修改脚手架配置,从而实现扩展 webpack 配置,如增加对 less 文件的支持、增加 antd 组件的按需加载、处理 html 文档中的图片路径问题等,甚至可以将单页面入口编译修改为多页面入口编译的方式。但是需要说明的是,我们可以通过 react-app-rewired 在不暴露配置文件的情况下达到扩展项目配置的目的,但并不建议使用这种方式进行多页面入口编译的配置,虽然本文主要就是介绍这种方案。本文的意义更多的是记录对这种方案的尝试,如果真的需要进行多页面配置,最好还是使用 npm eject 暴露配置文件的方式。主要原因是,通过 react-app-rewired 来实现多页面配置,是需要对脚手架原来的配置具有一定了解的,相较于 npm eject 暴露配置文件的方式来说,这种方式是不太具有透明度的,后面维护的难度较大。本文方案完成的基础是,我之前已经通过 npm eject 这种方式改造过一次多页面的配置,相当于说我已经拆过这个箱子了,我基本上了解了这个箱子里有什么,因此我在配置这个方案的过程中其实是对照着 npm eject 这个方案中的配置文件来逐步进行的。版本的变动使用 CRA 脚手架命令生成的项目免去了我们自己配置 webpack 的麻烦,其内置的各种配置所需要的插件和依赖包的版本都是确定的,是经过了检验的成熟配置,不会因为其中某个依赖包版本的问题造成构建出错。但当我们决定要自己动手配置 webpack 的时候,就意味着我们要自己根据需要安装一些 plugin 或 npm 依赖包,而这些插件和依赖包的版本可能不适用于当前 CRA 的版本,从而造成构建过程中出现一些不可预期的错误。因此,我们需要查看 CRA 脚手架项目的 package.json 文件,对于其中已经列出的 dependencies 依赖包,我们不应该改变这些依赖包的版本,而对于未列出的 npm 包,我们在使用的过程中需要逐个验证,不要一次性安装很多个 npm 包,否则执行构建命令的时候如果出错就会很难排查,最好是根据我们需要的功能逐个的安装相应的 npm 包,确定没有问题,再进行下一个功能的扩展。正是由于版本的变动会对配置产生很大的影响,因此当我们确定了一个配置方案之后,不要再轻易去改动其中涉及到的 npm 包的版本,通过 package-lock.json 文件锁定版本,防止配置方案错乱。另外,在本文编辑的时候,CRA 的最新版本为 v2.1.2,这个版本的 CRA 生成的项目,当前版本的 react-app-rewired v1.6.2 已经无法使用,具体信息可以参见 CRA >=2.1.2 breaking issue 2.1.2。至少在本文编辑的时候(2019.01.05),是无法适用于 CRA >=2.1.2 版本的,因此本文方案是基于最后一个可以适用的 CRA v2.1.1 版本来做的,package.json 文件中其他的 version 信息会附在方案后面。2019-01-11 补充:上面提到的版本基本都是指 package.json 文件中列出的依赖包的版本,但是严格来讲还应包含一些构建配置中使用的 node.js 工具包,比如 globby、dir-glob等,这些工具包的版本更新也有可能会造成构建出错。这种情况的出现往往是无法预期的,它们造成的影响一般比较广泛,而不仅仅是出现在我们方案配置的过程中,这种错误基本上都会在 Github 上有相应的 issue 以及解决方法或修复措施,本文中也列出了遇到的一个这种类型的错误,详见 四、错误排查 章节中的 其他错误 一节。二、准备工作创建一个 CRA v2.1.1 项目我们的配置方案是基于 CRA v2.1.1 脚手架项目进行改造的,因此首先我们要先创建一个 CRA 项目,详见官方文档:Create React App。但是这样创建出来的项目默认是最新版本的 CRA 项目,我们在上文中已经说明,我们的配置方案是要求特定版本的 CRA 项目的,那么如何创建特定的 CRA v2.1.1 版本项目?CRA 项目版本的核心其实就是 react-scripts 的版本,我们可以先创建一个最新版本的脚手架项目,然后更改为 v2.1.1 版本,具体如下:创建一个最新版本的 CRA 项目,参见官方文档:Create React App;删除 node_modules 文件夹,如果有 package-lock.json 文件或 yarn.lock 文件,也要一并删除,否则重新安装 node_modules 依赖包仍然会被锁定为原来的版本;修改 package.json 文件,重新指定 react-scripts 的版本:“dependencies”: { - “react”: “^16.7.0”, + “react”: “^16.6.3”, - “react-dom”: “^16.7.0”, + “react-dom”: “^16.6.3”, - “react-scripts”: “2.1.3” + “react-scripts”: “2.1.1”}执行 yarn install 或 npm install 重新安装项目依赖。至此,我们已经创建了一个 CRA v2.1.1 项目,这将作为我们进行多页面入口编译改造的基础。react-app-rewired 的用法首先要学习 react-app-rewired 的用法,参照官方文档:How to rewire your create-react-app project。主要是三点:安装 react-app-rewired创建一个 config-overrides.js 文件修改 package.json 文件中的脚本命令查看 CRA 项目原有的配置文件上面我们提到在进行多页面配置之前,需要对脚手架原有的配置文件具有一定了解,那么如何查看原有的配置文件?方法1:将使用 CRA 脚手架命令生成的项目拷贝一份,执行 npm eject 暴露配置文件,eject 之后文件组织结构如下:my-app├── config│ ├── jest│ ├── env.js│ ├── paths.js│ ├── webpack.config.dev.js│ ├── webpack.config.prod.js│ └── webpackDevServer.config.js├── node_modules├── public├── scripts│ ├── build.js│ ├── start.js│ └── test.js├── package.json├── README.md└── src其中 config 文件夹下就是脚手架原有的配置文件。方法2:使用 CRA 脚手架命令生成项目,在 my-app/node_modules/react-scripts/config 路径下可以看到原有的配置文件方法3:在 config-overrides.js 文件中将 webpack 配置对象输出在控制台,查看其结构推荐使用第 1 种方式,便于我们在配置过程中查看和操作,第 3 种方式作为一种辅助手段,主要是用于改造过程中的调试和验证配置对象的改造结果的。项目文件组织结构在开始进行多入口配置之前,需要先明确项目的文件组织结构,这关系到我们如何准确获取所有的入口文件,这里不再详述,请参见React-CRA 多页面配置(npm eject)中的项目文件组织结构一节。三、具体方案安装 react-app-rewired,创建 config-overrides.js 文件,修改 package.json 文件中的脚本命令。详见官方文档 How to rewire your create-react-app project。执行 yarn start 命令,查看 http://localhost:3000,确保安装 react-app-rewired 操作没有问题。修改文件组织结构。这里不再详述,参见上文 项目文件组织结构 一节。CRA项目原文件组织结构为: my-app ├── README.md ├── node_modules ├── package.json ├── config-overrides.js // 安装 react-app-rewired 时创建的 ├── .gitignore ├── public │ ├── favicon.ico │ ├── index.html │ └── manifest.json └── src ├── App.css ├── App.js ├── App.test.js ├── index.css ├── index.js ├── logo.svg └── serviceWorker.js修改为多页面入口编译的文件组织结构: // 本方案示例项目有两个页面 index.html & admin.html // 这里给出的文件组织结构是配置完成后的完整结构, 有些文件(如 src/setupProxy.js)的具体作用后面会给出说明 my-app ├── README.md ├── node_modules ├── package.json ├── config-overrides.js // 安装 react-app-rewired 时创建的 ├── .gitignore ├── public │ ├── favicon.ico │ ├── index.html // 作为所有页面的 html 模板文件 │ └── manifest.json └── src ├── index.js // 空白文件, 为了避免构建报错, 详见下文 ├── setupProxy.js // proxy 设置, 详见下文(在当前操作步骤中可以缺失) ├── index // index.html 页面对应的文件夹 │ ├── App.less │ ├── App.js │ ├── App.test.js │ ├── index.less // 使用 less 编写样式文件 │ ├── index.js │ ├── logo.svg │ └── serviceWorker.js └── admin // admin.html 页面对应的文件夹 ├── App.less ├── App.js ├── App.test.js ├── index.less // 使用 less 编写样式文件 ├── index.js ├── logo.svg └── serviceWorker.js执行 yarn start 命令,查看 http://localhost:3000/index.html 和 http://localhost:3000/admin.html,确保修改文件组织结构操作没有问题。这个示例项目是以 /my-app/public/index.html 作为所有页面的 html 模板文件的,当然也可以分别指定不同的 html 模板文件,这是根据项目需要和项目文件组织结构决定的。在这个示例项目中,由于作为模板的 html 文件只需要有个根元素即可,因此将其作为所有入口的 html 模板文件。这样的话,每个页面的 <title></title> 就需要在各自页面中分别指定,一般可以在页面挂载之后进行操作,比如: class App extends Component { componentDidMount() { document.title = ‘xxx’; } render() { return ( … ); } }修改 config-overrides.js 文件,进行具体配置。实际上我们之后所有的操作都是在这个文件中进行的。我们的测试方案是一个使用了 Ant Design、Redux、Less、Echarts 的多页面项目,需要达到以下要求:指定页面的多入口文件路径以及入口文件对应的 html 模板实现 antd 组件的按需加载增加对 less 文件的支持更改输出的文件名增加对 html 文档中图片路径的处理更改代码切割的配置设置别名路径上述每一个功能的实现,都需要执行 yarn start 或 yarn build 进行验证,确保操作成功后再进行下一项的配置。这里不再逐步说明配置的步骤,详见下面的代码及注释。最终 config-overrides.js 文件内容如下: /* config-overrides.js / / * @Author: mzhang.eric * @Last Modified time: 2019-01-10 18:37:17 * @Description: 使用 react-app-rewired 扩展和改造 CRA v2.1.1 项目, 基于 webpack v4.19.1 + react-app-rewired v1.6.2 版本 / const rewireLess = require(‘react-app-rewire-less’); const { injectBabelPlugin, paths } = require(‘react-app-rewired’); const HtmlWebpackPlugin = require(‘html-webpack-plugin’); const path = require(‘path’); const fs = require(‘fs’); const globby = require(‘globby’); const appDirectory = fs.realpathSync(process.cwd()); const resolveApp = relativePath => path.resolve(appDirectory, relativePath); // const BundleAnalyzerPlugin = require(‘webpack-bundle-analyzer’).BundleAnalyzerPlugin; module.exports = function override(config, env) { // 使用 babel-plugin-import 按需加载组件 config = injectBabelPlugin( [‘import’, { libraryName: ‘antd’, libraryDirectory: ’es’, style: true }], config, ); // 增加 less 支持 config = rewireLess.withLoaderOptions({ // 解决报错: Inline JavaScript is not enabled. Is it set in your options? javascriptEnabled: true, })(config, env); // 入口文件路径 // const entriesPath = globby.sync([resolveApp(‘src’) + ‘//index.js’]); const entriesPath = globby.sync([resolveApp(‘src’) + ‘//index.js’], {cwd: process.cwd()}); paths.entriesPath = entriesPath; // 获取指定路径下的入口文件 function getEntries(){ const entries = {}; const files = paths.entriesPath; files.forEach(filePath => { let tmp = filePath.split(’/’); let name = tmp[tmp.length - 2]; if(env === ‘production’){ entries[name] = [ filePath, ]; } else { entries[name] = [ require.resolve(‘react-dev-utils/webpackHotDevClient’), filePath, ]; } }); return entries; } // 入口文件对象 const entries = getEntries(); // 配置 HtmlWebpackPlugin 插件, 指定入口文件生成对应的 html 文件 let htmlPlugin; if(env === ‘production’){ htmlPlugin = Object.keys(entries).map(item => { return new HtmlWebpackPlugin({ inject: true, template: paths.appHtml, filename: item + ‘.html’, chunks: [item], minify: { removeComments: true, collapseWhitespace: true, removeRedundantAttributes: true, useShortDoctype: true, removeEmptyAttributes: true, removeStyleLinkTypeAttributes: true, keepClosingSlash: true, minifyJS: true, minifyCSS: true, minifyURLs: true, }, }); }); } else { htmlPlugin = Object.keys(entries).map(item => { return new HtmlWebpackPlugin({ inject: true, template: paths.appHtml, filename: item + ‘.html’, chunks: [item], }); }); } if (env === ‘production’) { for (let i = 0; i < config.plugins.length; i++) { let item = config.plugins[i]; // 更改输出的样式文件名 if (item.constructor.toString().indexOf(‘class MiniCssExtractPlugin’) > -1) { item.options.filename = ‘static/css/[name].css?_v=[contenthash:8]’; item.options.chunkFilename = ‘static/css/[name].chunk.css?_v=[contenthash:8]’; } // SWPrecacheWebpackPlugin: 使用 service workers 缓存项目依赖 if(item.constructor.toString().indexOf(‘function GenerateSW’) > -1){ // 更改输出的文件名 item.config.precacheManifestFilename = ‘precache-manifest.js?_v=[manifestHash]’; } } // 更改生产模式输出的文件名 config.output.filename = ‘static/js/[name].js?_v=[chunkhash:8]’; config.output.chunkFilename = ‘static/js/[name].chunk.js?_v=[chunkhash:8]’; } else { // 更改开发模式输出的文件名 config.output.filename = ‘static/js/[name].js’; config.output.chunkFilename = ‘static/js/[name].chunk.js’; } // 修改入口 config.entry = entries; // 修改 HtmlWebpackPlugin 插件 for (let i = 0; i < config.plugins.length; i++) { let item = config.plugins[i]; if (item.constructor.toString().indexOf(‘class HtmlWebpackPlugin’) > -1) { config.plugins.splice(i, 1); } } config.plugins.push(…htmlPlugin); // 分析打包内容 // config.plugins.push(new BundleAnalyzerPlugin()); // 设置别名路径 config.resolve.alias = { …config.resolve.alias, ‘@src’: paths.appSrc, // 在使用中, 有些 Eslint 规则会报错, 禁用这部分代码的 Eslint 检测即可 }; // 处理 html 文档中图片路径问题 config.module.rules[2].oneOf.push({ test: /.html$/, loader: ‘html-withimg-loader’ }); // 修改 build/static/media/ 路径下的文件名 for (let i = 0; i < config.module.rules[2].oneOf.length; i++) { const item = config.module.rules[2].oneOf[i]; if(!item.options || !item.options.name){ continue; } let str = item.options.name.toString(); if(str.indexOf(‘static/media/[name].[hash:8].[ext]’) > -1){ item.options.name = ‘static/media/[name].[ext]?_v=[hash:8]’; } } // 修改代码切割配置 // 参见 webpack 文档:https://webpack.js.org/plugins/split-chunks-plugin/#optimization-splitchunks config.optimization = { splitChunks: { // 将所有入口点共同使用到的、次数超过 2 次的模块,创建为一个名为 commons 的代码块 // 这种配置方式可能会增大初始的捆绑包,比如有些公共模块在首页其实并未用到,但也会打包进来,会降低首页的加载性能 // 建议将非必需模块使用 import() 的方式动态加载,提升页面的加载速度 // cacheGroups: { // commons: { // name: ‘commons’, // chunks: ‘initial’, // minChunks: 2 // } // } // 将所有使用到的 node_modules 中的模块包打包为 vendors 代码块。(不推荐) // 这种方式可能会产生一个包含所有外部依赖包的较大代码块,建议只包含核心框架和工具函数代码,其他依赖项动态加载 // cacheGroups: { // commons: { // test: /[\/]node_modules[\/]/, // name: ‘vendors’, // chunks: ‘all’ // } // } cacheGroups: { // 通过正则匹配,将 react react-dom echarts 等公共模块拆分为 vendor,可以通过 BundleAnalyzerPlugin 帮助确定拆分哪些模块包 vendor: { test: /[\/]node_modules\/[\/]/, name: ‘vendor’, chunks: ‘all’, // all, async, and initial }, // 将 css|less 文件合并成一个文件, mini-css-extract-plugin 的用法请参见文档:https://www.npmjs.com/package/mini-css-extract-plugin // MiniCssExtractPlugin 会将动态 import 引入的模块的样式文件也分离出去,将这些样式文件合并成一个文件可以提高渲染速度 // 其实如果可以不使用 mini-css-extract-plugin 这个插件,即不分离样式文件,可能更适合本方案,但是我没有找到方法去除这个插件 styles: { name: ‘styles’, test: /.css|less$/, chunks: ‘all’, // merge all the css chunk to one file enforce: true } }, }, }; return config; };四、错误排查这里对方案形成过程中遇到的错误进行记录,这些错误有些是配置过程中必定会出现的,是需要我们进行改造的,也有些错误可能是操作不当主观造成的。客观错误这类错误是进行多页面入口编译改造过程中必定会遇到的,有些是由于项目文件组织结构的改变造成的,也有些是为了扩展项目配置而使用了源 CRA 脚手架项目未提供的 npm 包造成的,前者会出现哪些错误我们是可以明确知道的,后者相对来说是不确切的,但大多都是版本不适用造成的。Could not find a required file.Could not find a required file. Name: index.js Searched in: C:xxxxxxmy-appsrc错误描述:通过 create-react-app 脚手架搭建的项目以 /src/index.js 作为应用入口,当执行构建脚本时如果检测到缺失了这个必要文件,node 进程会退出。但是在我们进行多页面入口改造的时候,src 直接路径下没有 index.js 文件,根据我们的文件组织结构,index.js 文件存在于每个独立页面的子文件夹下,如 /src/index/index.js /src/admin/index.js。解决方法:在 src 直接路径下创建一个空的 index.js 文件。Inline JavaScript is not enabled. Is it set in your options?// https://github.com/ant-design… .bezierEasingMixin();^ Inline JavaScript is not enabled. Is it set in your options? in C:xxxsrcmy-appnode_modulesantdesstylecolorbezierEasing.less (line 110, column 0)错误描述:当我们使用了 antd 组件的时候,构建报错,需要允许 less 文件中 js 的执行。解决方法:在 config-overrides.js 文件中增加 less 文件支持时,设置 javascriptEnabled 为 true。 module.exports = function override(config, env) { … // 增加 less 支持 config = rewireLess.withLoaderOptions({ // 解决报错: Inline JavaScript is not enabled. Is it set in your options? javascriptEnabled: true, })(config, env); … return config; };Failed to load resource: net::ERR_FILE_NOT_FOUND(build 版本)错误描述:执行 yarn build 或 npm run build 构建生产版本时,构建出的页面未能正确加载样式和脚本文件,chrome 检查工具报路径错误。解决方法:修改 package.json 文件,指定 homepage 字段的值,本项目这里指定为相对路径。 “homepage”: “./",When specified, “proxy” in package.json must be a string.When specified, “proxy” in package.json must be a string. Instead, the type of “proxy” was “object”. Either remove “proxy” from package.json, or make it a string.错误描述:我们在开发过程中一般会在 package.json 文件中配置 proxy 代理服务器,但是在 CRA 2.x 升级以后对 proxy 的设置做了修改,具体请参见官方升级文档:Move advanced proxy configuration to src/setupProxy.js解决方法:移除 package.json 文件中有关 proxy 的设置,使用 http-proxy-middleware,在 src 目录下创建 setupProxy.js 文件。详细方法请参见上述文档。主观错误这类错误应该是可以避免的,但是在配置过程中可能会由于开发人员的不当操作造成出错,最主要的就是安装了错误版本的 plugin 或 npm 包,我们上面已经说过,对于项目原本的配置中在 package.json 文件中已经列出的 plugin 和 npm 包,不要再重复进行安装,否则可能重新安装的 plugin 或 npm 包版本是不适用的,从而造成出错。例如,CRA v2.1.1 脚手架项目原有的 html-webpack-plugin 版本是 v4.0.0-alpha.2,是不需要再另外进行安装的。如果此时开发人员自己执行了 yarn add html-webpack-plugin,从而将 html-webpack-plugin 的版本更换为了 v3.2.0,这就会造成构建报错:URIError: Failed to decode param ‘/%PUBLIC_URL%/favicon.ico’ URIError: Failed to decode param ‘/%PUBLIC_URL%/manifest.json’,其他主观错误类似。html-webpack-plugin 版本错误URIError: Failed to decode param ‘/%PUBLIC_URL%/favicon.ico’ URIError: Failed to decode param ‘/%PUBLIC_URL%/manifest.json’错误描述:在执行 yarn start 构建命令时报错,页面空白。解决方法:html-webpack-plugin 版本错误,在本文的配置方案中,应当使用 “html-webpack-plugin”: “4.0.0-alpha.2”, 版本。TypeError: Cannot read property ‘state’ of undefined(页面报错)错误描述:编译构建过程没有报错,但页面报错:TypeError: Cannot read property ‘state’ of undefined。解决方法:redux 版本错误,在本文的配置方案中,应当使用 redux <=3.7.2 版本。其他错误我们在上文 版本的变动 一节的补充中已经对此有所提及,这类错误主要是由 node.js 工具包版本的升级造成的,这些错误基本都是不可预期的,也无法在这里全部涵盖,只能就当前遇到的问题进行简要记录,可能随着时间的推移,还会出现其他的类似问题,也可能这些错误已经在后续的版本中被修复了,因此请勿纠结于这里记录的错误,如果遇到了这类错误,就查阅资料进行修正,如果没有遇到,则无须理会。TypeError: Expected cwd to be of type string but received type undefinedC:xxxmy-appnode_modulesdir-globindex.js:59 throw new TypeError(Expected \cwdto be of typestringbut received type${typeof opts.cwd}``); TypeError: Expected cwd to be of type string but received type undefined错误描述:本文的写作开始于 2019-01-05,在 2019-01-11 重新审核本文方案的时候,遇到了这个错误,主要是由于 dir-glob 版本的升级造成的,我们在配置脚本中使用了 globby 的 sync 方法,dir-glob 版本升级之后,这个方法的调用会使得 dir-glob 抛出上述错误。详细信息参见:Broken build do to major change from 2.0 to 2.2 以及 globby will pass opts.cwd = undefined to dir-glob, which leads to TypeError.。解决方法:这里给出的解决方法是限定于当前时间的,因为在本文编辑的时候(2019-01-11)这个 issue 还没有给出最终的解决方案,个人觉得可能会由 globby 进行修复。 / config-overrides.js / // 修改获取入口文件路径的代码 - const entriesPath = globby.sync([resolveApp(‘src’) + ‘//index.js’]); + const entriesPath = globby.sync([resolveApp(‘src’) + ‘//index.js’], {cwd: process.cwd()});五、package.json 信息本文的多页面配置方案是基于 CRA 脚手架项目的,项目的依赖包信息会包含在两个 package.json 文件中,其中脚手架自带的配置信息位于 /my-app/node_modules/react-scripts/package.json 文件中,而我们根据项目需要自己增加的配置信息在 /my-app/package.json 文件中,这里将本方案项目配置信息附录如下: / package.json */ { “name”: “my-app”, “version”: “0.1.0”, “private”: true, “dependencies”: { “antd”: “^3.12.1”, “babel-plugin-import”: “^1.11.0”, “echarts”: “^4.2.0-rc.2”, “echarts-for-react”: “^2.0.15-beta.0”, “html-withimg-loader”: “^0.1.16”, “http-proxy-middleware”: “^0.19.1”, “react”: “^16.6.3”, “react-app-rewire-less”: “^2.1.3”, “react-app-rewired”: “^1.6.2”, “react-dom”: “^16.6.3”, “react-intl”: “^2.7.2”, “react-lazyload”: “^2.3.0”, “react-loadable”: “^5.5.0”, “react-redux”: “^6.0.0”, “react-scripts”: “2.1.1”, “redux”: “3.7.2”, “redux-promise-middleware”: “^5.1.1”, “webpack-bundle-analyzer”: “^3.0.3” }, “scripts”: { “start”: “react-app-rewired start”, “build”: “react-app-rewired build”, “test”: “react-app-rewired test –env=jsdom”, “eject”: “react-scripts eject” }, “eslintConfig”: { “extends”: “react-app” }, “browserslist”: [ “>0.2%”, “not dead”, “not ie <= 11”, “not op_mini all” ], “homepage”: “./” } ...
如果从头开始搭建React项目,create-react-app通常是开发者的首选。毕竟不是谁都有精力去了解WebPack的复杂配置,而CRA将配置隐藏开箱即用的特性必然会受到普遍欢迎。根目录访问到了部署阶段,我通常使用nginx作为web容器,将项目部署到一个根目录下访问。如# nginx配置server { listen 80; server_name my.website.com; … location / { alias /data/www/react-project/dist; index index.html }}那么只要我们将项目文件放到对应的目录下,重启nginx即可开始访问web页面。二级目录访问有时我们有多个web项目,多个项目不可能同时挂在根目录下,所以我们会划分二级目录来分别访问各个web项目。如http://my.website.com/project1 => 访问react-project1项目http://my.website.com/project2 => 访问react-project2项目问题1:CSS等资源加载失败此时,如果简单将nginx配置的location改为/project1,则会出现网页无法访问的错误。# nginx配置server { listen 80; server_name my.website.com; … # location / { location /project1 { alias /data/www/react-project/dist; index index.html }}现象从dev工具可以看出,html文件有取得,但css、js等资源引用失败。css和js的文件路径都是http://my.website.com/static/…(或css)。分析CRA(create-react-app)的项目配置默认是跑在根目录下的。如果查看dist目录下的html会发现,所有的css或js文件的引用路径都是/开头的绝对路径。解决将打包路径从绝对路径改为相对路径:# package.json{ … “homepage”: “.”, // 添加homepage属性,将路径改为当前目录 …}重新编译后看到,所有的资源文件路径都改过来了。问题2:加载成功,网页空白重新上传到服务器,更新dist目录下的文件,重启nginx后访问网页。现象结果发现,网页仍然是空白一片。查看html的渲染结果,发现似乎js并没有执行。分析在react-router-dom的例子中,通常使用的是BrowserRouter。这种类型的Router在向服务器发送请求时,如果相对于二级目录的路由去指向对应的页面路由,就会找不到资源,因此也就不会渲染。解决BrowserRouter有一个属性叫做basename,就是用于解决此类问题。…import { Route, BrowserRouter as Router, Switch, Redirect } from ‘react-router-dom’;…… <Router basename=’/project1’> <Switch> <Redirect exact key=‘index’ path=’/’ to=’/home’ /> { routes } </Switch> </Router>…问题3:访问成功,刷新后404修改以上配置并编译部署,重启nginx后可正常访问网页。但刷新后,网页变为一片空白。现象网页显示,在请求页面路由如http://my.website.com/project… 时,该路由的请求状态为404。分析还是因为BrowserRouter的问题,之前能正常访问时因为路由中设置了Redirect,所以能访问到根目录并自动跳转到/home。但直接访问则会访问失败。解决在nginx配置中加入try_files命令 location /project1 { alias /data/www/react-project/dist; # index index.html try_files $uri /project1/index.html }这样,在请求$uri时如果找不到对应的资源,会fallback回去加载index.html。问题解决。 ...
搭建项目框架新建项目执行如下代码,用create-react-app来建立项目的基础框架,然后安装需要用到的依赖。$ npx create-react-app my-test-project$ cd my-test-project$ yarn add react-router-dom react-redux prop-types redux redux-saga$ yarn start完成后,应用启动在localhost的3000端口。接入react-router-domreact-router-dom其实就是react-router 4.0,与之前的3.0有什么区别呢?react-router被一分为三。react-router、react-router-dom和react-router-native。react-router实现了路由的核心的路由组件和函数。而react-router-dom和react-router-native则是基于react-router,提供了特定的环境的组件。react-router-dom依赖react-router,安装的时候,不用再显示的安装react-router, 如果你有机会去看react-router-dom的源码,就会发现里面有些组件都是从react-router中引入的。新建layout在/src下新建layout目录。为什么要新建layout目录,因为有可能我们会用到多个layout,layout是一个什么样的概念?例如这个应用需要提供一部分功能在微信使用。那么进入所有微信的相关界面下都要进行鉴权。没有鉴权信息就不允许访问,但是这个服务仍然有所有人都可以访问的路由。使用layout可以很好的帮我们解决这个问题。将所有的需要鉴权的页面放在例如WechatContainer下,只有在有微信相关鉴权的信息存在,才允许访问接下来的界面,否则,容器内甚至可以直接不渲染接下来的界面。在/src/layout下新建两个文件,分别是AppLayout.js、WechatLayout.js。AppLayout.js的代码如下。在这个layout中,首页就是单纯的一个路由,导向至首页。而接下来的/wechat则是把路由导向至了一个微信端专用的layout。import React, { Component } from ‘react’;import Home from ‘../routes/home’;import WechatLayout from ‘./WechatLayout’;import { Route, Switch } from ‘react-router-dom’;/** * 项目入口布局 * 在此处根据一级路由的不同进入不同的container * 每个container有自己不同的作用 * * 在react-router V4中,将原先统一在一处的路由分散到各个模块中,分散到各个模块当中 * 例如: WechatLayout的路由为/wechat 表示到该layout下的默认路径 /class AppLayout extends Component { constructor(props) { super(props); this.state = {}; } render() { return ( <div className=‘App’> <main> <Switch> <Route path=’/’ exact component={Home} /> <Route path=’/wechat’ component={WechatLayout} /> </Switch> </main> </div> ); }}export default AppLayout;WechatLayout.js的代码如下。在这个layout中,我们就可以对访问该路由的用户进行鉴权。如果没有权限,我们可以直接限制用户的访问,甚至直接不渲染render中的数据。例如,我们可以在componentWillMount中或者在render中,根据当前的state数据,对当前用户进行鉴权。如果没有权限,我们就可以将当前页面重定向到没有权限的提示界面。import React, { Component } from ‘react’;import Home from ‘../routes/wechat/home’;import { Route, Switch } from ‘react-router-dom’;import { connect } from ‘react-redux’;class WechatLayout extends Component { constructor(props) { super(props); this.state = {}; } componentWillMount() { } render() { const className = ‘Wechat-Layout’; return ( <div className={${className}}> <header> Our Manage Layout </header> <main> <Switch> <Route path={${this.props.match.path}/home} component={Home} /> </Switch> </main> </div> ); }}const mapStateToProps = state => ({ reducer: state.wechatLayout});export default connect(mapStateToProps)(WechatLayout);新建routes新建/src/routes/home/index.js,代码如下。import React, { Component } from ‘react’;import {Link} from ‘react-router-dom’;class Home extends Component { constructor(props) { super(props); this.state = {}; } render() { const className = ‘Home’; return ( <div className={${className}}> <h1>This is Home</h1> <div><Link to={’/wechat/home’}>Manage Home</Link></div> </div> ); }}export default Home;新建/src/routes/wechat/home/index.js, 代码如下。在代码中可以看到,触发reducer很简单,只需要调用dispatch方法即可。dispatch中的payload就是该请求所带的参数,该参数会传到saga中间层,去调用真正的后端请求。并在请求返回成功之后,调用put方法更新state。import React, { Component } from ‘react’;import {connect} from “react-redux”;class Home extends Component { constructor(props) { super(props); this.state = {}; } componentWillMount() { this.props.dispatch({ type: ‘WATCH_GET_PROJECT’, payload: { projectName: ’tap4fun’ } }); } render() { const className = ‘Wechat-Home’; return ( <div className={${className}}> <h1>Home</h1> <h2>The project name is : { this.props.reducer.projectName }</h2> </div> ); }}const mapStateToProps = state => ({ reducer: state.wechat});export default connect(mapStateToProps)(Home)新建container在/src下新建container,在container中新建文件AppContainer.js。我们整个react应用都装在这个容器里面。AppContainer.js的代码如下。而其中的Provider组件,将包裹我们应用的容器AppLayout包在其中,使得下面的所有子组件都可以拿到state。Provider接受store参数作为props,然后通过context往下传递。import React, { Component } from ‘react’;import PropTypes from ‘prop-types’;import { Provider } from ‘react-redux’;import { BrowserRouter as Router } from ‘react-router-dom’;import AppLayout from ‘../layout/AppLayout’;class AppContainer extends Component { constructor(props) { super(props); this.state = {}; } static propTypes = { store: PropTypes.object.isRequired }; render() { const { store } = this.props; return ( <Provider store={store}> <Router> <AppLayout /> </Router> </Provider> ); }}export default AppContainer;修改项目入口文件更新/src/index.js,代码如下。在此处会将create出来的store容器当作属性传入到Appcontainer中,作为我们应用的状态容器。import React from ‘react’;import ReactDOM from ‘react-dom’;import ‘./index.css’;import * as serviceWorker from ‘./serviceWorker’;import AppContainer from ‘./container/AppContainer’;import createStore from ‘./store/createStore’;const store = createStore();ReactDOM.render(<AppContainer store={store} />, document.getElementById(‘root’));// If you want your app to work offline and load faster, you can change// unregister() to register() below. Note this comes with some pitfalls.// Learn more about service workers: http://bit.ly/CRA-PWAserviceWorker.unregister();新建store新建/src/store/craeteStore.js,代码如下。通过以下的方式,我们可以给redux添加很多中间件,甚至是自己写的中间件。比如,我们可以自己实现一个日志中间件,然后添加到中间件数组middleWares中,在创建redux的store的时候,应用我们自己写的中间件。import { applyMiddleware, compose, createStore } from ‘redux’;import createSagaMiddleware from ‘redux-saga’;import rootReducer from ‘../reducers’;import rootSaga from ‘../saga’;export default function configureStore(preloadedState) { // 创建saga中间件 const sagaMiddleware = createSagaMiddleware(); const middleWares = [sagaMiddleware]; const middlewareEnhancer = applyMiddleware(…middleWares); const enhancers = [middlewareEnhancer]; const composedEnhancers = compose(…enhancers); // 创建存储容器 const store = createStore(rootReducer, preloadedState, composedEnhancers); sagaMiddleware.run(rootSaga); return store;}在这引入了redux-saga。我之前在使用redux的时候,几乎在每个模块都要写相应的action和reducer,然后在相应的模块文件中引入action的函数,然后在使用mapDispatchToProps将该函数注入到props中,在相应的函数中调用。并且,一个action不能复用,即使触发的是相同的reducer。这样就会出现很多重复性的代码,新增一个模块的工作也相对繁琐了很多。但是使用了redux-saga之后,只需要在reducer中定义好相应类型的操作和saga就可以了。不需要定义action的函数,不需要在文件中引入action中函数,甚至连mapDispatchToProps都不需要,直接使用this.props.dispatch({ ’type’: ‘WATCH_GET_PROJECT’ })就可以调用。而且,action可以复用。新建saga新建/src/saga/index.js,代码如下。import { put, takeEvery } from ‘redux-saga/effects’;import { delay } from ‘redux-saga’;export function fetchProject() { yield delay(1000); yield put({ type: ‘GET_PROJECT’ })}export default function * rootSaga() { yield takeEvery(‘WATCH_GET_PROJECT’, fetchProject);}新建reducer新建/src/reducers/wechat.js,代码如下。const initialState = { projectName: null};export default function counter(state = initialState, action) { let newState = state; switch (action.type) { case ‘GET_PROJECT’: newState.projectName = action.payload.projectName; break; default: break; } return {…newState}}新建/src/reducers/index.js,代码如下。import { combineReducers } from ‘redux’;import Wechat from ‘./wechat’;export default combineReducers({ wechat: Wechat});在这里我们使用了combineReducers。在之前的基于redux的应用程序中,常见的state结构就是一个简单的JavaScript对象。重新启动应用到此处,重新启动应用,就可以在http://localhost:3000/wechat/home下看到从reducer中取出的数据。在页面中,我们就可以通过代码this.props.dispatch的方式,来触发action。参考https://github.com/mrdulin/bl…https://cn.redux.js.org/docs/…项目源代码Github仓库 ...