mirror of https://github.com/alibaba/ice.git
				
				
				
			feat: the next rax app framework
This commit is contained in:
		
							parent
							
								
									d2c5df448b
								
							
						
					
					
						commit
						907546003e
					
				|  | @ -29,6 +29,7 @@ const tsRules = deepmerge(tslint, { | |||
|     "@typescript-eslint/no-explicit-any": 0, | ||||
|     "@typescript-eslint/interface-name-prefix": 0, | ||||
|     "@typescript-eslint/explicit-function-return-type": 0, | ||||
|     "@typescript-eslint/no-var-requires": 0 | ||||
|   }, | ||||
| }); | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,4 +1,9 @@ | |||
| { | ||||
|   "web": { | ||||
|     "mpa": true, | ||||
|     "ssr": true | ||||
|   }, | ||||
|   "plugins": [], | ||||
|   "targets": ["web"] | ||||
|   "inlineStyle": true, | ||||
|   "targets": ["web", "weex"] | ||||
| } | ||||
|  |  | |||
|  | @ -1,15 +1,21 @@ | |||
| import { runApp } from 'rax-app'; | ||||
| 
 | ||||
| runApp({ | ||||
|   router: { | ||||
|     type: 'browser' | ||||
|   }, | ||||
|   app: { | ||||
|     onShow() { | ||||
|       console.log('app show...'); | ||||
|     }, | ||||
|     onHide() { | ||||
|       console.log('app hide...'); | ||||
|     }, | ||||
|     getInitialData: async () => { | ||||
|       return { | ||||
|         a: 1, | ||||
|         b: 2 | ||||
|       }; | ||||
|     } | ||||
|   }, | ||||
|   router: { | ||||
|     basename: '/home' | ||||
|   } | ||||
| }); | ||||
|  |  | |||
|  | @ -1,6 +1,5 @@ | |||
| import { createElement, PureComponent } from 'rax'; | ||||
| import Image from 'rax-image'; | ||||
| import { withRouter } from 'rax-app'; | ||||
| 
 | ||||
| import './index.css'; | ||||
| 
 | ||||
|  | @ -19,4 +18,4 @@ class Logo extends PureComponent { | |||
|   } | ||||
| } | ||||
| 
 | ||||
| export default withRouter(Logo); | ||||
| export default Logo; | ||||
|  |  | |||
|  | @ -1,7 +1,8 @@ | |||
| import { createElement } from 'rax'; | ||||
| import { Root, Style, Script } from 'rax-document'; | ||||
| import { Root, Style, Script, Data } from 'rax-document'; | ||||
| 
 | ||||
| function Document() { | ||||
| function Document(props) { | ||||
|   console.log('document props:', props); | ||||
|   return ( | ||||
|     <html> | ||||
|       <head> | ||||
|  | @ -13,6 +14,7 @@ function Document() { | |||
|       <body> | ||||
|         {/* root container */} | ||||
|         <Root /> | ||||
|         <Data /> | ||||
|         <Script /> | ||||
|       </body> | ||||
|     </html> | ||||
|  | @ -0,0 +1,11 @@ | |||
| { | ||||
|   "routes": [ | ||||
|     { | ||||
|       "path": "/", | ||||
|       "source": "/pages/About/index" | ||||
|     } | ||||
|   ], | ||||
|   "window": { | ||||
|     "title": "Rax App" | ||||
|   } | ||||
| } | ||||
|  | @ -0,0 +1,19 @@ | |||
| import { runApp } from 'rax-app'; | ||||
| import staticConfig from './app.json'; | ||||
| 
 | ||||
| runApp({ | ||||
|   app: { | ||||
|     onShow() { | ||||
|       console.log('app show...'); | ||||
|     }, | ||||
|     onHide() { | ||||
|       console.log('app hide...'); | ||||
|     }, | ||||
|     getInitialData: async () => { | ||||
|       return { | ||||
|         a: 1, | ||||
|         b: 2 | ||||
|       }; | ||||
|     } | ||||
|   } | ||||
| }, staticConfig); | ||||
|  | @ -5,6 +5,7 @@ import { getSearchParams, withPageLifeCycle } from 'rax-app'; | |||
| 
 | ||||
| import './index.css'; | ||||
| 
 | ||||
| @withPageLifeCycle | ||||
| class About extends PureComponent { | ||||
|   public componentDidMount() { | ||||
|     console.log('about search params', getSearchParams()); | ||||
|  | @ -21,11 +22,11 @@ class About extends PureComponent { | |||
|   public render() { | ||||
|     return ( | ||||
|       <View className="about"> | ||||
|         <Text className="title">About Page!!!</Text> | ||||
|         <Text className="info" onClick={() => this.props.history.push('/')}>Go Home</Text> | ||||
|         <Text className="title">About Page</Text> | ||||
|         <Text className="info" onClick={() => (this.props as any).history.push('/')}>Go Home</Text> | ||||
|       </View> | ||||
|     ); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| export default withPageLifeCycle(About); | ||||
| export default About; | ||||
|  |  | |||
|  | @ -4,7 +4,7 @@ | |||
| } | ||||
| 
 | ||||
| .title { | ||||
|   font-size: 45rpx; | ||||
|   font-size: 35rpx; | ||||
|   font-weight: bold; | ||||
|   margin: 20rpx 0; | ||||
| } | ||||
|  |  | |||
|  | @ -12,6 +12,7 @@ export default function Home(props) { | |||
|   const searchParams = getSearchParams(); | ||||
| 
 | ||||
|   console.log('home search params =>', searchParams); | ||||
|   console.log('home page props =>', props); | ||||
| 
 | ||||
|   usePageShow(() => { | ||||
|     console.log('home show...'); | ||||
|  | @ -24,9 +25,18 @@ export default function Home(props) { | |||
|   return ( | ||||
|     <View className="home"> | ||||
|       <Logo /> | ||||
|       <Text className="title">Welcome to Your Rax App!!!</Text> | ||||
|       <Text className="info">More information about Rax</Text> | ||||
|       <Text className="title">{props?.data?.title || 'Welcome to Your Rax App'}</Text> | ||||
|       <Text className="info">{props?.data?.info || 'More information about Rax'}</Text> | ||||
|       <Text className="info" onClick={() => history.push('/about?id=1')}>Go About</Text> | ||||
|     </View> | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
| Home.getInitialProps = async () => { | ||||
|   return { | ||||
|     data: { | ||||
|       title: 'Welcome to Your Rax App with SSR', | ||||
|       info: 'More information about Rax' | ||||
|     } | ||||
|   }; | ||||
| }; | ||||
|  |  | |||
|  | @ -64,6 +64,7 @@ | |||
|     "typescript": "^3.7.4" | ||||
|   }, | ||||
|   "dependencies": { | ||||
|     "core-js": "^3.6.4" | ||||
|     "core-js": "^3.6.4", | ||||
|     "path-to-regexp": "6.1.0" | ||||
|   } | ||||
| } | ||||
|  |  | |||
|  | @ -0,0 +1 @@ | |||
| # build-mpa-config | ||||
|  | @ -0,0 +1,29 @@ | |||
| { | ||||
|   "name": "build-mpa-config", | ||||
|   "version": "1.0.0-0", | ||||
|   "description": "enable mpa project for framework", | ||||
|   "author": "ice-admin@alibaba-inc.com", | ||||
|   "homepage": "", | ||||
|   "license": "MIT", | ||||
|   "main": "lib/index.js", | ||||
|   "directories": { | ||||
|     "lib": "lib" | ||||
|   }, | ||||
|   "files": [ | ||||
|     "lib" | ||||
|   ], | ||||
|   "publishConfig": { | ||||
|     "registry": "http://registry.npmjs.com/" | ||||
|   }, | ||||
|   "repository": { | ||||
|     "type": "git", | ||||
|     "url": "git@github.com:alibaba/ice.git" | ||||
|   }, | ||||
|   "scripts": { | ||||
|     "test": "echo \"Error: run tests from root\" && exit 1" | ||||
|   }, | ||||
|   "dependencies": { | ||||
|     "fs-extra": "^8.1.0", | ||||
|     "loader-utils": "^2.0.0" | ||||
|   } | ||||
| } | ||||
|  | @ -0,0 +1,37 @@ | |||
| import * as path from 'path'; | ||||
| import * as fs from 'fs-extra'; | ||||
| 
 | ||||
| function getEntries(rootDir: string) { | ||||
|   const pagesPath = path.join(rootDir, 'src/pages'); | ||||
|   const pages = fs.existsSync(pagesPath) | ||||
|     ? fs.readdirSync(pagesPath) | ||||
|       .filter(page => !/^[._]/.test(page)) | ||||
|       .map(page => path.parse(page).name) | ||||
|     : []; | ||||
|    | ||||
|    | ||||
|   const entries = pages.map((pageName) => { | ||||
|     const entryName = pageName.toLocaleLowerCase(); | ||||
|     const pageEntry = getPageEntry(pagesPath, pageName); | ||||
|     return { | ||||
|       entryName, | ||||
|       pageName, | ||||
|       entryPath: `${pageName}/${pageEntry}` | ||||
|     }; | ||||
|   }); | ||||
|   return entries; | ||||
| } | ||||
| 
 | ||||
| function getPageEntry(pagesPath: string, pageName: string) { | ||||
|   const pagePath = path.join(pagesPath, pageName); | ||||
|   const pageRootFiles = fs.readdirSync(pagePath); | ||||
|   const appRegexp = /^app\.(t|j)sx?$/; | ||||
|   const indexRegexp = /^index\.(t|j)sx?$/; | ||||
| 
 | ||||
|   return pageRootFiles.find(file => { | ||||
|     // eslint-disable-next-line
 | ||||
|     return appRegexp.test(file) ? 'app' : indexRegexp.test(file) ? 'index' : null; | ||||
|   }); | ||||
| } | ||||
| 
 | ||||
| export default getEntries; | ||||
|  | @ -0,0 +1,47 @@ | |||
| import * as path from 'path'; | ||||
| import getEntries from './getEntries'; | ||||
| 
 | ||||
| interface ConfigOptions { | ||||
|   context: { | ||||
|     rootDir: string; | ||||
|     commandArgs: any; | ||||
|   }; | ||||
|   type: string; | ||||
|   framework: string; | ||||
| } | ||||
| 
 | ||||
| const setMPAConfig = (config, options: ConfigOptions) => { | ||||
|   const { context, type = 'web', framework = 'rax' } = options || {}; | ||||
|   const { rootDir, commandArgs } = context; | ||||
|   let mpaEntries = getEntries(rootDir); | ||||
|   if (commandArgs.mpaEntry) { | ||||
|     const arr = commandArgs.mpaEntry.split(','); | ||||
|     mpaEntries = mpaEntries.filter((entry) => { | ||||
|       return arr.includes(entry.entryName); | ||||
|     }); | ||||
|   } | ||||
|   // do not splitChunks when mpa
 | ||||
|   config.optimization.splitChunks({ cacheGroups: {} }); | ||||
|   // clear entry points
 | ||||
|   config.entryPoints.clear(); | ||||
|   // add mpa entries
 | ||||
|   const matchStrs = []; | ||||
|   mpaEntries.forEach((entry) => { | ||||
|     const { entryName, entryPath, pageName } = entry; | ||||
|     const pageEntry = path.join(rootDir, 'src/pages', entryPath); | ||||
|     config.entry(entryName).add((/app\.(t|j)sx?$/.test(entryPath) || type === 'node') ? pageEntry : `${require.resolve('./mpa-loader')}?type=${type}&framework=${framework}!${pageEntry}`); | ||||
|     // get page paths for rule match
 | ||||
|     const matchStr = `src/pages/${pageName}`; | ||||
|     matchStrs.push(process.platform === 'win32' ? matchStr.replace(/\//g, '\\\\') : matchStr); | ||||
|   }); | ||||
|   // modify appJSON rules for mpa
 | ||||
|   if (config.module.rules.get('appJSON')) { | ||||
|     const matchInclude = (filepath: string) => { | ||||
|       const matchReg = matchStrs.length ? new RegExp(matchStrs.join('|')) : null; | ||||
|       return matchReg && matchReg.test(filepath); | ||||
|     }; | ||||
|     config.module.rule('appJSON').include.add(matchInclude); | ||||
|   } | ||||
| }; | ||||
| 
 | ||||
| export default setMPAConfig; | ||||
|  | @ -0,0 +1,40 @@ | |||
| import { getOptions } from 'loader-utils'; | ||||
| 
 | ||||
| function mpaLoader() { | ||||
|   const options = getOptions(this) || {}; | ||||
|   const framework = options.framework || 'rax'; | ||||
|   let appRender = ''; | ||||
|   if (options.type === 'weex') { | ||||
|     appRender = 'render(createElement(Entry), null, { driver: DriverUniversal });'; | ||||
|   } else { | ||||
|     appRender = ` | ||||
|       const renderApp = async function() { | ||||
|         // process App.getInitialProps
 | ||||
|         if (isSSR && window.__INITIAL_DATA__.pageData !== null) { | ||||
|           Object.assign(comProps, window.__INITIAL_DATA__.pageData); | ||||
|         } else if (Component.getInitialProps) { | ||||
|           Object.assign(comProps, await Component.getInitialProps()); | ||||
|         } | ||||
|         render(createElement(Entry), document.getElementById("root"), { driver: DriverUniversal, hydrate: isSSR }); | ||||
|       } | ||||
| 
 | ||||
|       renderApp(); | ||||
|     `;
 | ||||
|   } | ||||
|   const source = ` | ||||
|   import { render, createElement } from '${framework}'; | ||||
|   import Component from '${process.platform === 'win32' ? this.resourcePath.replace(/\//g, '\\\\') : this.resourcePath}'; | ||||
|   import DriverUniversal from 'driver-universal'; | ||||
|   const isSSR = window.__INITIAL_DATA__ && window.__INITIAL_DATA__.__SSR_ENABLED__; | ||||
| 
 | ||||
|   const comProps = {}; | ||||
| 
 | ||||
|   function Entry() { | ||||
|     return createElement(Component, comProps); | ||||
|   } | ||||
|   ${appRender} | ||||
|   `;
 | ||||
|   return source; | ||||
| } | ||||
| 
 | ||||
| export default mpaLoader; | ||||
|  | @ -0,0 +1,8 @@ | |||
| { | ||||
|   "extends": "../../tsconfig.settings.json", | ||||
|   "compilerOptions": { | ||||
|     "baseUrl": "./", | ||||
|     "rootDir": "src", | ||||
|     "outDir": "lib" | ||||
|   } | ||||
| } | ||||
|  | @ -35,7 +35,7 @@ const configCSSRule = (config, style, mode, loaders = []) => { | |||
|           .exclude.add(cssModuleReg).end(); | ||||
|     } | ||||
| 
 | ||||
|     if (mode === 'development') {       | ||||
|     if (mode === 'development') { | ||||
|       const cssHotLoader = rule.use('css-hot-loader') | ||||
|         .loader(require.resolve('css-hot-loader')); | ||||
|       if (ruleKey === 'module') { | ||||
|  |  | |||
|  | @ -3,5 +3,8 @@ import { getHistory } from './history'; | |||
| 
 | ||||
| export default function() { | ||||
|   const history = getHistory(); | ||||
|   return queryString.parse(history.location.search); | ||||
|   if (history && history.location) { | ||||
|     return queryString.parse(history.location.search); | ||||
|   } | ||||
|   return {}; | ||||
| } | ||||
|  |  | |||
|  | @ -4,7 +4,7 @@ import { | |||
|   createMemoryHistory | ||||
| } from 'history'; | ||||
| import { createMiniAppHistory } from 'miniapp-history'; | ||||
| import { isMiniAppPlatform } from './env'; | ||||
| import { isMiniAppPlatform, isWeex, isKraken } from './env'; | ||||
| 
 | ||||
| // eslint-disable-next-line
 | ||||
| let history; | ||||
|  | @ -12,16 +12,22 @@ let history; | |||
| function createHistory({ routes, customHistory, type, basename }: any) { | ||||
|   if (customHistory) { | ||||
|     history = customHistory; | ||||
|   } else if (type === 'hash') { | ||||
|     history = createHashHistory({ basename }); | ||||
|   } else if (type === 'browser') { | ||||
|     history = createBrowserHistory({ basename }); | ||||
|   } else if (isMiniAppPlatform) { | ||||
|     (window as any).history = createMiniAppHistory(routes); | ||||
|     window.location = (window.history as any).location; | ||||
|     history = window.history; | ||||
|   } else { | ||||
|     history = createMemoryHistory(); | ||||
|     // Force memory history when env is weex or kraken
 | ||||
|     if (isWeex || isKraken) { | ||||
|       type = 'memory'; | ||||
|     } | ||||
|     if (type === 'hash') { | ||||
|       history = createHashHistory({ basename }); | ||||
|     } else if (type === 'browser') { | ||||
|       history = createBrowserHistory({ basename }); | ||||
|     } else if (isMiniAppPlatform) { | ||||
|       (window as any).history = createMiniAppHistory(routes); | ||||
|       window.location = (window.history as any).location; | ||||
|       history = window.history; | ||||
|     } else { | ||||
|       history = createMemoryHistory(); | ||||
|     } | ||||
|   } | ||||
|   return history; | ||||
| } | ||||
|  |  | |||
|  | @ -12,27 +12,46 @@ const configPath = path.resolve(rawArgv.config || 'build.json'); | |||
| 
 | ||||
| const inspectRegExp = /^--(inspect(?:-brk)?)(?:=(?:([^:]+):)?(\d+))?$/; | ||||
| 
 | ||||
| function modifyInspectArgv(argv) { | ||||
|   return Promise.all( | ||||
|     argv.map(async item => { | ||||
| async function modifyInspectArgv(execArgv, processArgv) { | ||||
|   /** | ||||
|    * Enable debugger by exec argv, eg. node --inspect node_modules/.bin/build-scripts start | ||||
|    * By this way, there will be two inspector, because start.js is run as a child process. | ||||
|    * So need to handle the conflict of port. | ||||
|    */ | ||||
|   const result = await Promise.all( | ||||
|     execArgv.map(async item => { | ||||
|       const matchResult = inspectRegExp.exec(item); | ||||
|       if (!matchResult) { | ||||
|         return item; | ||||
|       } | ||||
|       // eslint-disable-next-line no-unused-vars
 | ||||
|       const [_, command, ip, port = 9229] = matchResult; | ||||
|       const nPort = +port; | ||||
|       const newPort = await detect(nPort); | ||||
|       return `--${command}=${ip ? `${ip}:` : ''}${newPort}`; | ||||
|     }) | ||||
|   ); | ||||
| 
 | ||||
|   /** | ||||
|    * Enable debugger by process argv, eg. npm run start --inspect | ||||
|    * Need to change it as an exec argv. | ||||
|    */ | ||||
|   if (processArgv.inspect) { | ||||
|     const matchResult = /(?:([^:]+):)?(\d+)/.exec(rawArgv.inspect); | ||||
|     const [_, ip, port = 9229] = matchResult || []; | ||||
|     const newPort = await detect(port); | ||||
|     result.push(`--inspect-brk=${ip ? `${ip}:` : ''}${newPort}`); | ||||
|   } | ||||
| 
 | ||||
|   return result; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| function restartProcess(forkChildProcessPath) { | ||||
|   (async () => { | ||||
|     // remove the inspect related argv when passing to child process to avoid port-in-use error
 | ||||
|     const argv = await modifyInspectArgv(process.execArgv); | ||||
|     child = fork(forkChildProcessPath, process.argv.slice(2), { execArgv: argv }); | ||||
|     const argv = await modifyInspectArgv(process.execArgv, rawArgv); | ||||
|     const nProcessArgv = process.argv.slice(2).filter((arg) => arg.indexOf('--inspect') === -1); | ||||
|     child = fork(forkChildProcessPath, nProcessArgv, { execArgv: argv }); | ||||
|     child.on('message', data => { | ||||
|       if (process.send) { | ||||
|         process.send(data); | ||||
|  |  | |||
|  | @ -0,0 +1,64 @@ | |||
| { | ||||
|   "name": "build-plugin-app-base", | ||||
|   "version": "1.0.0-0", | ||||
|   "description": "rax app base plugin", | ||||
|   "main": "lib/index.js", | ||||
|   "scripts": { | ||||
|     "test": "echo \"Error: no test specified\" && exit 1" | ||||
|   }, | ||||
|   "keywords": [ | ||||
|     "plugin", | ||||
|     "rax" | ||||
|   ], | ||||
|   "author": "", | ||||
|   "license": "MIT", | ||||
|   "peerDependencies": { | ||||
|     "@alib/build-scripts": "^0.1.0", | ||||
|     "rax": "^1.0.0" | ||||
|   }, | ||||
|   "dependencies": { | ||||
|     "@babel/core": "7.2.0", | ||||
|     "@babel/generator": "^7.9.6", | ||||
|     "@reactml/loader": "^0.1.2", | ||||
|     "add-asset-html-webpack-plugin": "^3.1.3", | ||||
|     "autoprefixer": "^9.4.3", | ||||
|     "babel-loader": "8.0.4", | ||||
|     "chalk": "^2.4.2", | ||||
|     "core-js": "^3.3.1", | ||||
|     "css-loader": "^3.4.2", | ||||
|     "debug": "^4.1.1", | ||||
|     "eslint-loader": "^4.0.0", | ||||
|     "fork-ts-checker-webpack-plugin": "^5.2.0", | ||||
|     "friendly-errors-webpack-plugin": "^1.7.0", | ||||
|     "fs-extra": "^8.1.0", | ||||
|     "loader-utils": "^1.1.0", | ||||
|     "lodash": "^4.17.15", | ||||
|     "memory-fs": "^0.5.0", | ||||
|     "miniapp-builder-shared": "^0.1.5", | ||||
|     "mkcert": "^1.2.0", | ||||
|     "null-loader": "^4.0.0", | ||||
|     "path-exists": "^4.0.0", | ||||
|     "postcss": "^7.0.17", | ||||
|     "postcss-import": "^12.0.1", | ||||
|     "postcss-plugin-rpx2vw": "^0.0.2", | ||||
|     "postcss-preset-env": "^6.7.0", | ||||
|     "qrcode-terminal": "^0.12.0", | ||||
|     "rax-babel-config": "1.0.0-0", | ||||
|     "rax-jest-config": "^1.0.0", | ||||
|     "rax-webpack-config": "1.0.0-0", | ||||
|     "react-dev-utils": "^10.2.1", | ||||
|     "regenerator-runtime": "^0.13.3", | ||||
|     "stylesheet-loader": "^0.8.5", | ||||
|     "terser": "^4.6.4", | ||||
|     "terser-webpack-plugin": "^2.1.3", | ||||
|     "ts-loader": "^5.3.3", | ||||
|     "typescript": "^3.2.4", | ||||
|     "webpack": "^4.27.1", | ||||
|     "webpack-bundle-analyzer": "^3.6.0", | ||||
|     "webpack-chain": "^6.0.0", | ||||
|     "webpack-dev-mock": "^1.0.0", | ||||
|     "webpack-plugin-import": "^0.2.6", | ||||
|     "webpack-sources": "^1.3.0", | ||||
|     "webpackbar": "^4.0.0" | ||||
|   } | ||||
| } | ||||
|  | @ -0,0 +1,64 @@ | |||
| const getWebpackConfig = require('rax-webpack-config'); | ||||
| const getBabelConfig = require('rax-babel-config'); | ||||
| const ProgressPlugin = require('webpackbar'); | ||||
| 
 | ||||
| module.exports = (api, { target, babelConfigOptions, progressOptions }) => { | ||||
|   const { context } = api; | ||||
|   const { rootDir, command, webpack, commandArgs } = context; | ||||
|   const appMode = commandArgs.mode || command; | ||||
|   const babelConfig = getBabelConfig(babelConfigOptions); | ||||
| 
 | ||||
|   const mode = command === 'start' ? 'development' : 'production'; | ||||
|   const config = getWebpackConfig({ | ||||
|     rootDir, | ||||
|     mode, | ||||
|     babelConfig, | ||||
|     target | ||||
|   }); | ||||
|   // 1M = 1024 KB = 1048576 B
 | ||||
|   config.performance.maxAssetSize(1048576).maxEntrypointSize(1048576); | ||||
| 
 | ||||
|   // setup DefinePlugin, HtmlWebpackPlugin and  CopyWebpackPlugin out of onGetWebpackConfig
 | ||||
|   // in case of registerUserConfig will be excute before onGetWebpackConfig
 | ||||
| 
 | ||||
|   // DefinePlugin
 | ||||
|   const defineVariables = { | ||||
|     'process.env.NODE_ENV': JSON.stringify(mode || 'development'), | ||||
|     'process.env.APP_MODE': JSON.stringify(appMode), | ||||
|     'process.env.SERVER_PORT': JSON.stringify(commandArgs.port), | ||||
|   }; | ||||
| 
 | ||||
|   config | ||||
|     .plugin('ProgressPlugin') | ||||
|     .use(ProgressPlugin, [Object.assign({ color: '#F4AF3D' } ,progressOptions)]) | ||||
|     .end() | ||||
|     .plugin('DefinePlugin') | ||||
|     .use(webpack.DefinePlugin, [defineVariables]) | ||||
|     .end(); | ||||
| 
 | ||||
|   // Process app.json file
 | ||||
|   config.module | ||||
|     .rule('appJSON') | ||||
|     .type('javascript/auto') | ||||
|     .test(/app\.json$/) | ||||
|     .use('babel-loader') | ||||
|     .loader(require.resolve('babel-loader')) | ||||
|     .options(babelConfig) | ||||
|     .end() | ||||
|     .use('loader') | ||||
|     .loader(require.resolve('./loaders/AppConfigLoader')); | ||||
| 
 | ||||
|   ['jsx', 'tsx'].forEach((ruleName) => { | ||||
|     config.module | ||||
|       .rule(ruleName) | ||||
|       .use('platform-loader') | ||||
|       .loader(require.resolve('rax-compile-config/src/platformLoader')); | ||||
|   }); | ||||
| 
 | ||||
|   if (command === 'start') { | ||||
|     // disable build-scripts stats output
 | ||||
|     process.env.DISABLE_STATS = true; | ||||
|   } | ||||
| 
 | ||||
|   return config; | ||||
| }; | ||||
|  | @ -0,0 +1,8 @@ | |||
| const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer'); | ||||
| 
 | ||||
| module.exports = (config, analyzer) => { | ||||
|   if (analyzer) { | ||||
|     config.plugin('webpack-bundle-analyzer') | ||||
|       .use(BundleAnalyzerPlugin, [{ analyzerPort: '9000' }]); | ||||
|   } | ||||
| }; | ||||
|  | @ -0,0 +1,8 @@ | |||
| module.exports = (config, port) => { | ||||
|   if (port && config.plugins.get('webpack-bundle-analyzer')) { | ||||
|     config.plugin('webpack-bundle-analyzer').tap(([args]) => { | ||||
|       const newArgs = {...args, analyzerPort: port }; | ||||
|       return [newArgs]; | ||||
|     }); | ||||
|   } | ||||
| }; | ||||
|  | @ -0,0 +1,13 @@ | |||
| module.exports = (config, disableReload) => { | ||||
|   if (disableReload) { | ||||
|     config.plugins.delete('HotModuleReplacementPlugin'); | ||||
| 
 | ||||
|     // remove css hot loader of scss/module-scss/css/module-css/less/module-less
 | ||||
|     ['scss', 'scss-module', 'css', 'css-module', 'less', 'less-module'].forEach((rule) => { | ||||
|       if (config.module.rules.get(rule)) { | ||||
|         config.module.rule(rule).uses.delete('css-hot-loader'); | ||||
|       } | ||||
|     }); | ||||
|     config.devServer.hot(false).inline(false); | ||||
|   } | ||||
| }; | ||||
|  | @ -0,0 +1,21 @@ | |||
| const getCertificate = require('../utils/getCertificate'); | ||||
| 
 | ||||
| module.exports = async(config, https) => { | ||||
|   let httpsConfig; | ||||
|   if (https) { | ||||
|     try { | ||||
|       const cert = await getCertificate(); | ||||
|       httpsConfig = { | ||||
|         key: cert.key, | ||||
|         cert: cert.cert, | ||||
|       }; | ||||
|     } catch (e) { | ||||
|       console.log('HTTPS certificate generation failed and has been converted to HTTP.'); | ||||
|     } | ||||
|   } | ||||
|   if (httpsConfig) { | ||||
|     config.devServer.https(httpsConfig); | ||||
|   } else { | ||||
|     config.devServer.https(false); | ||||
|   } | ||||
| }; | ||||
|  | @ -0,0 +1,63 @@ | |||
| module.exports = { | ||||
|   alias: {}, | ||||
|   define: {}, | ||||
|   devPublicPath: '/', | ||||
|   filename: '[name].js', | ||||
|   // resolve.extensions
 | ||||
|   extensions: ['.js', '.jsx', '.json', '.html', '.ts', '.tsx', 'rml'], | ||||
|   // resolve.modules
 | ||||
|   modules: ['node_modules'], | ||||
|   devServer: { | ||||
|     disableHostCheck: true, | ||||
|     compress: true, | ||||
|     // Use 'ws' instead of 'sockjs-node' on server since webpackHotDevClient is using native websocket
 | ||||
|     transportMode: 'ws', | ||||
|     logLevel: 'silent', | ||||
|     clientLogLevel: 'none', | ||||
|     hot: true, | ||||
|     publicPath: '/', | ||||
|     quiet: false, | ||||
|     watchOptions: { | ||||
|       ignored: /node_modules/, | ||||
|       aggregateTimeout: 600, | ||||
|     }, | ||||
|     before(app) { | ||||
|       app.use((req, res, next) => { | ||||
|         // set cros for all served files
 | ||||
|         res.set('Access-Control-Allow-Origin', '*'); | ||||
|         next(); | ||||
|       }); | ||||
|     }, | ||||
|   }, | ||||
|   mock: true, | ||||
|   externals: {}, | ||||
|   hash: false, | ||||
|   injectBabel: 'polyfill', | ||||
|   minify: true, | ||||
|   outputAssetsPath: { | ||||
|     js: '', | ||||
|     css: '', | ||||
|   }, | ||||
|   outputDir: 'build', | ||||
|   proxy: {}, | ||||
|   publicPath: '/', | ||||
|   browserslist: 'last 2 versions, Firefox ESR, > 1%, ie >= 9, iOS >= 8, Android >= 4', | ||||
|   vendor: true, | ||||
|   libraryTarget: '', | ||||
|   library: '', | ||||
|   libraryExport: '', | ||||
|   sourceMap: false, | ||||
|   terserOptions: {}, | ||||
|   cssLoaderOptions: {}, | ||||
|   lessLoaderOptions: {}, | ||||
|   sassLoaderOptions: {}, | ||||
|   postcssrc: false, | ||||
|   compileDependencies: [], | ||||
|   babelPlugins: [], | ||||
|   babelPresets: [], | ||||
|   eslint: false, | ||||
|   tsChecker: false, | ||||
|   dll: false, | ||||
|   dllEntry: {}, | ||||
|   inlineStyle: false | ||||
| }; | ||||
|  | @ -0,0 +1,32 @@ | |||
| module.exports = { | ||||
|   https: { | ||||
|     commands: ['start'], | ||||
|   }, | ||||
|   analyzer: { | ||||
|     commands: ['start', 'build'], | ||||
|   }, | ||||
|   'analyzer-port': { | ||||
|     commands: ['start', 'build'], | ||||
|     module: 'analyzerPort', | ||||
|   }, | ||||
|   'disable-reload': { | ||||
|     commands: ['start'], | ||||
|     module: 'disableReload', | ||||
|   }, | ||||
|   'disable-mock': { | ||||
|     module: false, | ||||
|     commands: ['start'], | ||||
|   }, | ||||
|   'disable-open': { | ||||
|     module: false, | ||||
|     commands: ['start'], | ||||
|   }, | ||||
|   'mode': { | ||||
|     module: false, | ||||
|     commands: ['start', 'build'], | ||||
|   }, | ||||
|   'disable-assets': { | ||||
|     module: false, | ||||
|     commands: ['start'], | ||||
|   }, | ||||
| }; | ||||
|  | @ -0,0 +1,75 @@ | |||
| const assert = require('assert'); | ||||
| const { isPlainObject } = require('lodash'); | ||||
| 
 | ||||
| const validation = (key, value, types) => { | ||||
|   const validateResult = types.split('|').some((type) => { | ||||
|     if (type === 'array') { | ||||
|       return Array.isArray(value); | ||||
|     } else if (type === 'object') { | ||||
|       return isPlainObject(value); | ||||
|     } else { | ||||
|       // eslint-disable-next-line valid-typeof
 | ||||
|       return typeof value === type; | ||||
|     } | ||||
|   }); | ||||
|   assert(validateResult, `Config ${key} should be ${types.replace('|', ' | ')}, but got ${value}`); | ||||
|   return validateResult; | ||||
| }; | ||||
| 
 | ||||
| module.exports = { | ||||
|   alias: 'object', | ||||
|   define: 'object', | ||||
|   devPublicPath: 'string', | ||||
|   filename: 'string', | ||||
|   extensions: 'array', | ||||
|   modules: 'array', | ||||
|   devServer: 'object', | ||||
|   entry: (val) => { | ||||
|     // entry: string | array
 | ||||
|     // entry : { [name]: string | array }
 | ||||
|     return validation('entry', val, 'string|array|object'); | ||||
|   }, | ||||
|   externals: 'object', | ||||
|   hash: (val) => { | ||||
|     return validation('hash', val, 'string|boolean'); | ||||
|   }, | ||||
|   injectBabel: (val) => { | ||||
|     return validation('injectBabel', val, 'string|boolean'); | ||||
|   }, | ||||
|   minify: 'boolean', | ||||
|   mock: 'boolean', | ||||
|   outputAssetsPath: 'object', | ||||
|   outputDir: 'string', | ||||
|   proxy: 'object', | ||||
|   publicPath: 'string', | ||||
|   targets: 'array', | ||||
|   browserslist: (val) => { | ||||
|     return validation('browserslist', val, 'string|object'); | ||||
|   }, | ||||
|   vendor: 'boolean', | ||||
|   library: (val) => { | ||||
|     return validation('library', val, 'string|object'); | ||||
|   }, | ||||
|   libraryTarget: 'string', | ||||
|   libraryExport: (val) => { | ||||
|     return validation('library', val, 'string|array'); | ||||
|   }, | ||||
|   ignoreHtmlTemplate: 'boolean', | ||||
|   sourceMap: 'boolean', | ||||
|   terserOptions: 'object', | ||||
|   cssLoaderOptions: 'object', | ||||
|   lessLoaderOptions: 'object', | ||||
|   sassLoaderOptions: 'object', | ||||
|   postcssrc: 'boolean', | ||||
|   compileDependencies: 'array', | ||||
|   babelPlugins: 'array', | ||||
|   babelPresets: 'array', | ||||
|   eslint: (val) => { | ||||
|     return validation('eslint', val, 'boolean|object'); | ||||
|   }, | ||||
|   tsChecker: 'boolean', | ||||
|   dll: 'boolean', | ||||
|   // dllEntry: { [string]: string[] }
 | ||||
|   dllEntry: 'object', | ||||
|   inlineStyle: 'boolean' | ||||
| }; | ||||
|  | @ -0,0 +1,11 @@ | |||
| module.exports = { | ||||
|   WEB: 'web', | ||||
|   DOCUMENT: 'document', | ||||
|   WEEX: 'weex', | ||||
|   KRAKEN: 'kraken', | ||||
|   MINIAPP: 'miniapp', | ||||
|   WECHAT_MINIPROGRAM: 'wechat-miniprogram', | ||||
|   BYTEDANCE_MICROAPP: 'bytedance-microapp', | ||||
|   QUICKAPP: 'quickapp', | ||||
|   GET_WEBPACK_BASE_CONFIG: 'getWebpackBaseConfig' | ||||
| }; | ||||
|  | @ -0,0 +1,46 @@ | |||
| const path = require('path'); | ||||
| const registerCliOption = require('./registerCliOption'); | ||||
| const registerUserConfig = require('./registerUserConfig'); | ||||
| const modifyUserConfig = require('./modifyUserConfig'); | ||||
| const getBase = require('./base'); | ||||
| const dev = require('./setDev'); | ||||
| const build = require('./setBuild'); | ||||
| const test = require('./setTest'); | ||||
| const { GET_WEBPACK_BASE_CONFIG } = require('./constants'); | ||||
| 
 | ||||
| module.exports = (api) => { | ||||
|   const { | ||||
|     onGetWebpackConfig, | ||||
|     context, | ||||
|     setValue, | ||||
|   } = api; | ||||
|   const { command, rootDir } = context; | ||||
|   setValue(GET_WEBPACK_BASE_CONFIG, getBase); | ||||
| 
 | ||||
|   // register cli option
 | ||||
|   registerCliOption(api); | ||||
| 
 | ||||
|   // register user config
 | ||||
|   registerUserConfig(api); | ||||
| 
 | ||||
|   // modify user config to keep excute order
 | ||||
|   modifyUserConfig(api); | ||||
| 
 | ||||
|   // set webpack config
 | ||||
|   onGetWebpackConfig((chainConfig) => { | ||||
|     // add resolve modules of project node_modules
 | ||||
|     chainConfig.resolve.modules.add(path.join(rootDir, 'node_modules')); | ||||
|   }); | ||||
| 
 | ||||
|   if (command === 'test') { | ||||
|     test(api); | ||||
|   } | ||||
| 
 | ||||
|   if (command === 'start') { | ||||
|     dev(api); | ||||
|   } | ||||
| 
 | ||||
|   if (command === 'build') { | ||||
|     build(api); | ||||
|   } | ||||
| }; | ||||
|  | @ -0,0 +1,17 @@ | |||
| const { join } = require('path'); | ||||
| const { existsSync } = require('fs-extra'); | ||||
| 
 | ||||
| /** | ||||
|  * ./pages/foo -> based on src, return original | ||||
|  * /pages/foo -> based on rootContext | ||||
|  * pages/foo -> based on src, add prefix: './' | ||||
|  */ | ||||
| module.exports = function getDepPath(path, rootContext = '') { | ||||
|   if (path[0] === '.') { | ||||
|     return path; | ||||
|   } else if (path[0] === '/') { | ||||
|     return join(rootContext, 'src', path); | ||||
|   } else { | ||||
|     return `./${path}`; | ||||
|   } | ||||
| }; | ||||
|  | @ -0,0 +1,102 @@ | |||
| const { getOptions } = require('loader-utils'); | ||||
| const getRouteName = require('../../utils/getRouteName'); | ||||
| const getDepPath = require('./getDepPath'); | ||||
| 
 | ||||
| /** | ||||
|  * universal-app-config-loader | ||||
|  * return { | ||||
|  *  "routes": [ | ||||
|       { | ||||
|         "path": "/", | ||||
|         "source": "pages/Home/index", | ||||
|         "component": fn, | ||||
|       } | ||||
|     ], | ||||
|     "shell": { | ||||
|       "source": "shell/index", | ||||
|       "component": fn | ||||
|     }, | ||||
|     "hydrate": false | ||||
|   } | ||||
|  */ | ||||
| module.exports = function(appJSON) { | ||||
|   const options = getOptions(this) || {}; | ||||
|   const { type } = options; | ||||
|   const appConfig = JSON.parse(appJSON); | ||||
| 
 | ||||
|   if (!appConfig.routes || !Array.isArray(appConfig.routes)) { | ||||
|     throw new Error('routes should be an array in app.json.'); | ||||
|   } | ||||
| 
 | ||||
|   appConfig.routes = appConfig.routes.filter(route => { | ||||
|     if (Array.isArray(route.targets) && !route.targets.includes(type)) { | ||||
|       return false; | ||||
|     } | ||||
| 
 | ||||
|     return true; | ||||
|   }); | ||||
| 
 | ||||
|   const assembleRoutes = appConfig.routes.map((route) => { | ||||
|     if (!route.path || !route.source) { | ||||
|       throw new Error('route object should have path and source.'); | ||||
|     } | ||||
| 
 | ||||
|     // Set page title: Web use document.title; Weex need Native App support title api;
 | ||||
|     // Default route title: appConfig.window.title
 | ||||
|     let routeTitle = appConfig.window && appConfig.window.title ? appConfig.window.title : ''; | ||||
|     if (route.window && route.window.title) { | ||||
|       // Current route title: route.window.title
 | ||||
|       routeTitle = route.window.title; | ||||
|     } | ||||
| 
 | ||||
|     // First level function to support hooks will autorun function type state,
 | ||||
|     // Second level function to support rax-use-router rule autorun function type component.
 | ||||
|     const dynamicImportComponent = | ||||
|       `(routeProps) =>
 | ||||
|       import(/* webpackChunkName: "${getRouteName(route, this.rootContext).toLocaleLowerCase()}.chunk" */ '${getDepPath(route.source, this.rootContext)}') | ||||
|       .then((mod) => () => { | ||||
|         const reference = interopRequire(mod); | ||||
|         function Component(props) { | ||||
|           return createElement(reference, Object.assign({}, routeProps, props)); | ||||
|         } | ||||
|         ${routeTitle ? `document.title="${routeTitle}"` : ''} | ||||
|         Component.__path = '${route.path}'; | ||||
|         return Component; | ||||
|       }) | ||||
|     `;
 | ||||
|     const importComponent = `() => () => interopRequire(require('${getDepPath(route.source, this.rootContext)}'))`; | ||||
| 
 | ||||
|     return `routes.push(
 | ||||
|       { | ||||
|         ...${JSON.stringify(route)}, | ||||
|         component: ${type === 'web' ? dynamicImportComponent : importComponent} | ||||
|       } | ||||
|     );`;
 | ||||
|   }).join('\n'); | ||||
| 
 | ||||
|   let processShell; | ||||
|   if (appConfig.shell) { | ||||
|     processShell = ` | ||||
|     import Shell from "${getDepPath(appConfig.shell.source, this.rootContext)}"; | ||||
|     appConfig.shell = { | ||||
|       source: '${appConfig.shell.source}', | ||||
|       component: Shell | ||||
|     }; | ||||
|     `;
 | ||||
|   } else { | ||||
|     processShell = ''; | ||||
|   } | ||||
| 
 | ||||
|   return ` | ||||
|     import { createElement } from 'rax'; | ||||
|     const interopRequire = (mod) => mod && mod.__esModule ? mod.default : mod; | ||||
|     const routes = []; | ||||
|     ${assembleRoutes} | ||||
|     const appConfig = { | ||||
|       ...${appJSON}, | ||||
|       routes | ||||
|     }; | ||||
|     ${processShell} | ||||
|     export default appConfig; | ||||
|   `;
 | ||||
| }; | ||||
|  | @ -0,0 +1,21 @@ | |||
| const defaultConfig = require('./config/default.config'); | ||||
| 
 | ||||
| module.exports = (api) => { | ||||
|   const { modifyUserConfig } = api; | ||||
| 
 | ||||
|   // modify user config to keep excute order
 | ||||
|   modifyUserConfig((userConfig) => { | ||||
|     const configKeys = [...Object.keys(userConfig), 'filename'].sort(); | ||||
|     const newConfig = {}; | ||||
|     configKeys.forEach((configKey) => { | ||||
|       if (!['plugins', 'web', 'miniapp', 'weex', 'kraken', 'wechat-miniprogram', 'targets'].includes(configKey)) { | ||||
|         newConfig[configKey] = Object.prototype.hasOwnProperty.call(userConfig, configKey) | ||||
|           ? userConfig[configKey] | ||||
|           : defaultConfig[configKey]; | ||||
|         delete userConfig[configKey]; | ||||
|       } | ||||
|     }); | ||||
| 
 | ||||
|     return newConfig; | ||||
|   }); | ||||
| }; | ||||
|  | @ -0,0 +1,42 @@ | |||
| /* eslint-disable no-case-declarations */ | ||||
| /* eslint-disable global-require */ | ||||
| const atImport = require('postcss-import'); | ||||
| 
 | ||||
| // See https://github.com/postcss/postcss-loader#context-ctx
 | ||||
| module.exports = ({ file, options, env }) => { | ||||
|   const type = options && options.type; | ||||
|   return { | ||||
|     plugins: getPlugins(type), | ||||
|   }; | ||||
| }; | ||||
| 
 | ||||
| function getPlugins(type) { | ||||
|   switch (type) { | ||||
|     case 'normal': | ||||
|       return [ | ||||
|         atImport() | ||||
|       ]; | ||||
|     // Inline style
 | ||||
|     case 'web-inline': | ||||
|       return [ | ||||
|         require('postcss-plugin-rpx2vw')(), | ||||
|         atImport() | ||||
|       ]; | ||||
| 
 | ||||
|     // extract css file in web while inlineStyle is disabled
 | ||||
|     // web standard
 | ||||
|     case 'web': | ||||
|       return [ | ||||
|         require('postcss-preset-env')({ | ||||
|           autoprefixer: { | ||||
|             flexbox: 'no-2009', | ||||
|           }, | ||||
|           stage: 3, | ||||
|         }), | ||||
|         require('postcss-plugin-rpx2vw')(), | ||||
|         atImport() | ||||
|       ]; | ||||
|     default: | ||||
|       return []; | ||||
|   } | ||||
| } | ||||
|  | @ -0,0 +1,23 @@ | |||
| const optionConfig = require('./config/option.config'); | ||||
| 
 | ||||
| module.exports = (api) => { | ||||
|   const { registerCliOption, log } = api; | ||||
|   const optionKeys = Object.keys(optionConfig); | ||||
|   registerCliOption(optionKeys.map((optionKey) => { | ||||
|     const { module, commands } = optionConfig[optionKey]; | ||||
|     const moduleName = module || optionKey; | ||||
|     const optionDefination = { | ||||
|       name: optionKey, | ||||
|       commands, | ||||
|     }; | ||||
|     if (module !== false) { | ||||
|       try { | ||||
|         // eslint-disable-next-line
 | ||||
|         optionDefination.configWebpack = require(`./cliOption/${moduleName}`); | ||||
|       } catch (err) { | ||||
|         log.error(err); | ||||
|       } | ||||
|     } | ||||
|     return optionDefination; | ||||
|   })); | ||||
| }; | ||||
|  | @ -0,0 +1,44 @@ | |||
| const defaultConfig = require('./config/default.config'); | ||||
| const validation = require('./config/validation'); | ||||
| 
 | ||||
| const CONFIG = [ | ||||
|   { | ||||
|     name: 'modeConfig', | ||||
|     validation: 'object', | ||||
|     defaultValue: {}, | ||||
|   }, | ||||
|   { | ||||
|     name: 'web', | ||||
|     validation: 'object', | ||||
|     defaultValue: {} | ||||
|   } | ||||
| ]; | ||||
| 
 | ||||
| module.exports = (api) => { | ||||
|   const { registerUserConfig, log } = api; | ||||
|   CONFIG.forEach((item) => registerUserConfig(item)); | ||||
| 
 | ||||
|   // sort config key to make sure entry config is always excute before injectBabel
 | ||||
|   const configKeys = Object.keys(defaultConfig).sort(); | ||||
|   // register user config
 | ||||
|   registerUserConfig(configKeys.map((configKey) => { | ||||
|     let configFunc = null; | ||||
|     let configValidation = null; | ||||
|     try { | ||||
|       // eslint-disable-next-line
 | ||||
|       configFunc = require(`./userConfig/${configKey}`); | ||||
|       configValidation = validation[configKey]; | ||||
|     } catch (err) { | ||||
|       log.error(err); | ||||
|     } | ||||
|     if (configFunc && configValidation) { | ||||
|       return { | ||||
|         name: configKey, | ||||
|         validation: configValidation, | ||||
|         configWebpack: configFunc, | ||||
|         defaultValue: defaultConfig[configKey], | ||||
|       }; | ||||
|     } | ||||
|     return false; | ||||
|   }).filter(Boolean)); | ||||
| }; | ||||
|  | @ -0,0 +1,99 @@ | |||
| const path = require('path'); | ||||
| const formatWebpackMessages = require('react-dev-utils/formatWebpackMessages'); | ||||
| const chalk = require('chalk'); | ||||
| const { | ||||
|   MINIAPP, | ||||
|   WEB, | ||||
|   WECHAT_MINIPROGRAM, | ||||
|   BYTEDANCE_MICROAPP, | ||||
|   WEEX, | ||||
|   KRAKEN, | ||||
| } = require('./constants'); | ||||
| 
 | ||||
| const highlightPrint = chalk.hex('#F4AF3D'); | ||||
| 
 | ||||
| module.exports = (api) => { | ||||
|   // eslint-disable-next-line global-require
 | ||||
|   const debug = require('debug')('rax-app'); | ||||
|   const { context, onHook } = api; | ||||
|   const { rootDir, userConfig } = context; | ||||
| 
 | ||||
|   onHook('before.build.run', ({ config }) => { | ||||
|     try { | ||||
|       debug(config[0]); | ||||
|     // eslint-disable-next-line no-empty
 | ||||
|     } catch (err) {} | ||||
|   }); | ||||
| 
 | ||||
|   onHook('after.build.compile', ({ err, stats }) => { | ||||
|     const statsJson = stats.toJson({ | ||||
|       all: false, | ||||
|       errors: true, | ||||
|       warnings: true, | ||||
|       timings: true, | ||||
|     }); | ||||
|     const messages = formatWebpackMessages(statsJson); | ||||
|     // Do not print localUrl and assets information when containing an error
 | ||||
|     const isSuccessful = !messages.errors.length; | ||||
|     const { outputDir = 'build', targets } = userConfig; | ||||
| 
 | ||||
|     if (isSuccessful) { | ||||
|       console.log(highlightPrint('Build finished:')); | ||||
|       console.log(); | ||||
| 
 | ||||
|       if (targets.includes(WEB)) { | ||||
|         console.log(highlightPrint('[Web] Bundle at:')); | ||||
|         console.log( | ||||
|           '   ', | ||||
|           chalk.underline.white(path.resolve(rootDir, outputDir, WEB)) | ||||
|         ); | ||||
|         console.log(); | ||||
|       } | ||||
| 
 | ||||
|       if (targets.includes(WEEX)) { | ||||
|         console.log(highlightPrint('[Weex] Bundle at:')); | ||||
|         console.log( | ||||
|           '   ', | ||||
|           chalk.underline.white(path.resolve(rootDir, outputDir, WEEX)) | ||||
|         ); | ||||
|         console.log(); | ||||
|       } | ||||
| 
 | ||||
|       if (targets.includes(KRAKEN)) { | ||||
|         console.log(highlightPrint('[Kraken] Bundle at:')); | ||||
|         console.log( | ||||
|           '   ', | ||||
|           chalk.underline.white(path.resolve(rootDir, outputDir, KRAKEN)) | ||||
|         ); | ||||
|         console.log(); | ||||
|       } | ||||
| 
 | ||||
|       if (targets.includes(MINIAPP)) { | ||||
|         console.log(highlightPrint('[Alibaba MiniApp] Bundle at:')); | ||||
|         console.log( | ||||
|           '   ', | ||||
|           chalk.underline.white(path.resolve(rootDir, outputDir, MINIAPP)) | ||||
|         ); | ||||
|         console.log(); | ||||
|       } | ||||
| 
 | ||||
|       if (targets.includes(WECHAT_MINIPROGRAM)) { | ||||
|         console.log(highlightPrint('[WeChat MiniProgram] Bundle at:')); | ||||
|         console.log( | ||||
|           '   ', | ||||
|           chalk.underline.white(path.resolve(rootDir, outputDir, WECHAT_MINIPROGRAM)) | ||||
|         ); | ||||
|         console.log(); | ||||
|       } | ||||
| 
 | ||||
|       if (targets.includes(BYTEDANCE_MICROAPP)) { | ||||
|         console.log(highlightPrint('[ByteDance MicroApp] Bundle at:')); | ||||
|         console.log( | ||||
|           '   ', | ||||
|           chalk.underline.white(path.resolve(rootDir, outputDir, BYTEDANCE_MICROAPP)) | ||||
|         ); | ||||
|         console.log(); | ||||
|       } | ||||
|     } | ||||
|   }); | ||||
| }; | ||||
|  | @ -0,0 +1,147 @@ | |||
| const formatWebpackMessages = require('react-dev-utils/formatWebpackMessages'); | ||||
| const openBrowser = require('react-dev-utils/openBrowser'); | ||||
| const chalk = require('chalk'); | ||||
| const qrcode = require('qrcode-terminal'); | ||||
| const path = require('path'); | ||||
| 
 | ||||
| const { | ||||
|   MINIAPP, | ||||
|   WEB, | ||||
|   WECHAT_MINIPROGRAM, | ||||
|   BYTEDANCE_MICROAPP, | ||||
|   WEEX, | ||||
|   KRAKEN, | ||||
| } = require('./constants'); | ||||
| 
 | ||||
| const highlightPrint = chalk.hex('#F4AF3D'); | ||||
| 
 | ||||
| module.exports = function(api) { | ||||
|   // eslint-disable-next-line global-require
 | ||||
|   const debug = require('debug')('rax-app'); | ||||
|   const { context, onHook } = api; | ||||
|   const { commandArgs, userConfig, rootDir } = context; | ||||
|   const { targets } = userConfig; | ||||
|   let webEntryKeys = []; | ||||
|   let weexEntryKeys = []; | ||||
|   const getWebpackEntry = (configs, configName) => (configs.find((webpackConfig) => webpackConfig.name === configName).entry || {}); | ||||
|   onHook('before.start.run', ({ config }) => { | ||||
|     webEntryKeys = Object.keys(getWebpackEntry(config, 'web')); | ||||
|     weexEntryKeys = Object.keys(getWebpackEntry(config, 'weex')); | ||||
|     try { | ||||
|       debug(config[0]); | ||||
|     // eslint-disable-next-line no-empty
 | ||||
|     } catch (err) {} | ||||
|   }); | ||||
| 
 | ||||
|   onHook('after.start.compile', async({ urls, stats, err }) => { | ||||
|     const statsJson = stats.toJson({ | ||||
|       all: false, | ||||
|       errors: true, | ||||
|       warnings: true, | ||||
|       timings: true, | ||||
|     }); | ||||
|     const messages = formatWebpackMessages(statsJson); | ||||
|     // Do not print localUrl and assets information when containing an error
 | ||||
|     const isSuccessful = !messages.errors.length; | ||||
|     const { outputDir = 'build' } = userConfig; | ||||
| 
 | ||||
| 
 | ||||
|     if (isSuccessful) { | ||||
|       if (commandArgs.disableAssets === false) { | ||||
|         console.log( | ||||
|           stats.toString({ | ||||
|             errors: false, | ||||
|             warnings: false, | ||||
|             colors: true, | ||||
|             assets: true, | ||||
|             chunks: false, | ||||
|             entrypoints: false, | ||||
|             modules: false, | ||||
|             timings: false, | ||||
|           }) | ||||
|         ); | ||||
|       } | ||||
| 
 | ||||
|       if (targets.includes(MINIAPP)) { | ||||
|         console.log( | ||||
|           highlightPrint( | ||||
|             '  [Alibaba Miniapp] Use ali miniapp developer tools to open the following folder:' | ||||
|           ) | ||||
|         ); | ||||
|         console.log( | ||||
|           '   ', | ||||
|           chalk.underline.white(path.resolve(rootDir, outputDir, MINIAPP)) | ||||
|         ); | ||||
|         console.log(); | ||||
|       } | ||||
| 
 | ||||
|       if (targets.includes(WECHAT_MINIPROGRAM)) { | ||||
|         console.log( | ||||
|           highlightPrint( | ||||
|             '  [WeChat MiniProgram] Use wechat miniprogram developer tools to open the following folder:' | ||||
|           ) | ||||
|         ); | ||||
|         console.log( | ||||
|           '   ', | ||||
|           chalk.underline.white(path.resolve(rootDir, outputDir, WECHAT_MINIPROGRAM)) | ||||
|         ); | ||||
|         console.log(); | ||||
|       } | ||||
| 
 | ||||
|       if (targets.includes(BYTEDANCE_MICROAPP)) { | ||||
|         console.log( | ||||
|           highlightPrint( | ||||
|             '  [Bytedance Microapp] Use bytedance microapp developer tools to open the following folder:' | ||||
|           ) | ||||
|         ); | ||||
|         console.log( | ||||
|           '   ', | ||||
|           chalk.underline.white(path.resolve(rootDir, outputDir, BYTEDANCE_MICROAPP)) | ||||
|         ); | ||||
|         console.log(); | ||||
|       } | ||||
|       if (targets.includes(WEB)) { | ||||
|         console.log(highlightPrint('  [Web] Development server at: ')); | ||||
|         webEntryKeys.forEach((entryKey) => { | ||||
|           const entryPath = webEntryKeys.length > 0 ? entryKey : ''; | ||||
|           console.log(`  ${chalk.underline.white(`${getLocalUrl(urls.localUrlForBrowser)}${entryPath}`)}`); | ||||
|           console.log(`  ${chalk.underline.white(`${getLocalUrl(urls.lanUrlForBrowser)}${entryPath}`)}`); | ||||
|           console.log(); | ||||
|         }); | ||||
|       } | ||||
| 
 | ||||
|       if (targets.includes(KRAKEN)) { | ||||
|         const krakenURL = `${urls.localUrlForBrowser  }kraken/index.js`; | ||||
|         console.log(highlightPrint('  [Kraken] Development server at: ')); | ||||
|         console.log(`  ${chalk.underline.white(krakenURL)}`); | ||||
|         console.log(); | ||||
|         console.log(highlightPrint('  [Kraken] Run Kraken Playground App: ')); | ||||
|         console.log(`  ${chalk.underline.white(`kraken -u ${krakenURL}`)}`); | ||||
|         console.log(); | ||||
|       } | ||||
| 
 | ||||
|       if (targets.includes(WEEX)) { | ||||
|         // Use Weex App to scan ip address (mobile phone can't visit localhost).
 | ||||
|         console.log(highlightPrint('  [Weex] Development server at: ')); | ||||
|         weexEntryKeys.forEach((entryKey) => { | ||||
|           const weexUrl = `${urls.lanUrlForBrowser}weex/${weexEntryKeys.length > 0 ? entryKey : 'index'}.js?wh_weex=true`; | ||||
|           console.log(`  ${chalk.underline.white(weexUrl)}`); | ||||
|           console.log(); | ||||
|           qrcode.generate(weexUrl, { small: true }); | ||||
|           console.log(); | ||||
|         }); | ||||
|       } | ||||
|     } | ||||
|   }); | ||||
| 
 | ||||
|   if (!commandArgs.disableOpen && targets.includes[WEB]) { | ||||
|     onHook('after.start.devServer', ({ url }) => { | ||||
|       // do not open browser when restart dev
 | ||||
|       if (!process.env.RESTART_DEV) openBrowser(getLocalUrl(url)); | ||||
|     }); | ||||
|   } | ||||
| }; | ||||
| 
 | ||||
| function getLocalUrl(url, entryHtml) { | ||||
|   return entryHtml ? `${url}${entryHtml}` : url; | ||||
| } | ||||
|  | @ -0,0 +1,34 @@ | |||
| const getJestConfig = require('rax-jest-config'); | ||||
| 
 | ||||
| module.exports = (api) => { | ||||
|   // eslint-disable-next-line global-require
 | ||||
|   const debug = require('debug')('rax-app'); | ||||
|   const { onGetJestConfig, context, onHook } = api; | ||||
|   const { rootDir } = context; | ||||
| 
 | ||||
|   onHook('before.test.run', ({ config }) => { | ||||
|     debug(JSON.stringify(config, null, 2)); | ||||
|   }); | ||||
| 
 | ||||
|   onGetJestConfig((jestConfig) => { | ||||
|     const { moduleNameMapper, ...rest } = jestConfig; | ||||
| 
 | ||||
|     Object.keys(moduleNameMapper).forEach((key) => { | ||||
|       // escape $ in the beginning. because $ match the end position end in regular expression
 | ||||
|       // '^$ice/history$' -> '^\$ice/history$'
 | ||||
|       if (key.indexOf('^$') === 0) { | ||||
|         const newKey = `^\\${key.slice(1)}`; | ||||
|         moduleNameMapper[newKey] = moduleNameMapper[key]; | ||||
|         delete moduleNameMapper[key]; | ||||
|       } | ||||
|     }); | ||||
| 
 | ||||
|     const defaultJestConfig = getJestConfig({ rootDir, moduleNameMapper }); | ||||
|     return { | ||||
|       ...defaultJestConfig, | ||||
|       ...rest, | ||||
|       // defaultJestConfig.moduleNameMapper already combine jestConfig.moduleNameMapper
 | ||||
|       moduleNameMapper: defaultJestConfig.moduleNameMapper, | ||||
|     }; | ||||
|   }); | ||||
| }; | ||||
|  | @ -0,0 +1,18 @@ | |||
| const path = require('path'); | ||||
| 
 | ||||
| module.exports = (config, alias, context) => { | ||||
|   const { rootDir } = context; | ||||
|   const aliasWithRoot = {}; | ||||
|   Object.keys(alias).forEach((key) => { | ||||
|     if (path.isAbsolute(alias[key])) { | ||||
|       aliasWithRoot[key] = alias[key]; | ||||
|     } else { | ||||
|       aliasWithRoot[key] = path.resolve(rootDir, alias[key]); | ||||
|     } | ||||
|   }); | ||||
|   config.merge({ | ||||
|     resolve: { | ||||
|       alias: aliasWithRoot, | ||||
|     }, | ||||
|   }); | ||||
| }; | ||||
|  | @ -0,0 +1,17 @@ | |||
| module.exports = (config, babelPlugins) => { | ||||
|   ['jsx', 'tsx'].forEach((rule) => { | ||||
|     config.module | ||||
|       .rule(rule) | ||||
|       .use('babel-loader') | ||||
|       .tap((options) => { | ||||
|         const { plugins = [] } = options; | ||||
|         return { | ||||
|           ...options, | ||||
|           plugins: [ | ||||
|             ...plugins, | ||||
|             ...babelPlugins, | ||||
|           ], | ||||
|         }; | ||||
|       }); | ||||
|   }); | ||||
| }; | ||||
|  | @ -0,0 +1,33 @@ | |||
| const formatWinPath = require('../utils/formatWinPath'); | ||||
| 
 | ||||
| module.exports = (config, babelPresets) => { | ||||
|   ['jsx', 'tsx'].forEach((rule) => { | ||||
|     config.module | ||||
|       .rule(rule) | ||||
|       .use('babel-loader') | ||||
|       .tap((options) => { | ||||
|         let extraPresets = [...babelPresets]; | ||||
|         const presets = options.presets.map((preset) => { | ||||
|           const [presetPath] = Array.isArray(preset) ? preset : [preset]; | ||||
|           let matchedPreset = null; | ||||
|           extraPresets = extraPresets.filter((babelPreset) => { | ||||
|             const matched = formatWinPath(presetPath).indexOf(Array.isArray(babelPreset) ? babelPreset[0] : babelPreset) > -1; | ||||
|             if (matched) { | ||||
|               matchedPreset = babelPreset; | ||||
|             } | ||||
|             return !matched; | ||||
|           }); | ||||
|           // replace current preset if match
 | ||||
|           return matchedPreset || preset; | ||||
|         }); | ||||
| 
 | ||||
|         return { | ||||
|           ...options, | ||||
|           presets: [ | ||||
|             ...presets, | ||||
|             ...extraPresets, | ||||
|           ], | ||||
|         }; | ||||
|       }); | ||||
|   }); | ||||
| }; | ||||
|  | @ -0,0 +1,22 @@ | |||
| const formatWinPath = require('../utils/formatWinPath'); | ||||
| 
 | ||||
| module.exports = (config, browserslist) => { | ||||
|   ['jsx', 'tsx'].forEach((rule) => { | ||||
|     config.module | ||||
|       .rule(rule) | ||||
|       .use('babel-loader') | ||||
|       .tap((options) => { | ||||
|         const babelPresets = options.presets || []; | ||||
|         const presets = babelPresets.map((preset) => { | ||||
|           if (Array.isArray(preset) && formatWinPath(preset[0]).indexOf(formatWinPath('@babel/preset-env')) > -1) { | ||||
|             return [ | ||||
|               preset[0], | ||||
|               Object.assign(preset[1], { targets: browserslist }), | ||||
|             ]; | ||||
|           } | ||||
|           return preset; | ||||
|         }); | ||||
|         return Object.assign(options, { presets }); | ||||
|       }); | ||||
|   }); | ||||
| }; | ||||
|  | @ -0,0 +1,41 @@ | |||
| const defaultCompileDependencies = [ | ||||
|   'ansi-regex', | ||||
|   'ansi-styles', | ||||
|   'chalk', | ||||
|   'query-string', | ||||
|   'react-dev-utils', | ||||
|   'split-on-first', | ||||
|   'strict-uri-encode', | ||||
|   'strip-ansi' | ||||
| ]; | ||||
| module.exports = (config, compileDependencies) => { | ||||
|   const matchExclude = (filepath) => { | ||||
|     // exclude the core-js for that it will fail to run in IE
 | ||||
|     if (filepath.match(/core-js/)) | ||||
|       return true; | ||||
|     // compile build-plugin module for default
 | ||||
|     const deps = [/build-plugin.*module/].concat(defaultCompileDependencies, compileDependencies).map(dep => { | ||||
|       if (dep instanceof RegExp) { | ||||
|         return dep.source; | ||||
|       } else if (typeof dep === 'string') { | ||||
|         // add default node_modules
 | ||||
|         const matchStr = `node_modules/?.+${dep}/`; | ||||
|         return process.platform === 'win32' ? matchStr.replace(/\//g, '\\\\') : matchStr; | ||||
|       } | ||||
|       return false; | ||||
|     }).filter(Boolean); | ||||
|     const matchReg = deps.length ? new RegExp(deps.join('|')) : null; | ||||
|     if (matchReg && matchReg.test(filepath)) { | ||||
|       return false; | ||||
|     } | ||||
|     // exclude node_modules as default
 | ||||
|     return /node_modules/.test(filepath); | ||||
|   }; | ||||
| 
 | ||||
|   ['jsx', 'tsx'].forEach((rule) => { | ||||
|     config.module | ||||
|       .rule(rule) | ||||
|       .exclude.clear() | ||||
|       .add(matchExclude); | ||||
|   }); | ||||
| }; | ||||
|  | @ -0,0 +1,22 @@ | |||
| module.exports = (config, cssLoaderOptions) => { | ||||
|   if (cssLoaderOptions) { | ||||
|     [ | ||||
|       'scss', | ||||
|       'scss-module', | ||||
|       'css', | ||||
|       'css-module', | ||||
|       'less', | ||||
|       'less-module', | ||||
|     ].forEach(rule => { | ||||
|       if (config.module.rules.get(rule)) { | ||||
|         config.module | ||||
|           .rule(rule) | ||||
|           .use('css-loader') | ||||
|           .tap((options) => ({ | ||||
|             ...options, | ||||
|             ...cssLoaderOptions, | ||||
|           })); | ||||
|       } | ||||
|     }); | ||||
|   } | ||||
| }; | ||||
|  | @ -0,0 +1,12 @@ | |||
| module.exports = (config, define) => { | ||||
|   if (config.plugins.get('DefinePlugin')) { | ||||
|     const defineVariables = {}; | ||||
|     // JSON.stringify define values
 | ||||
|     Object.keys(define).forEach((defineKey) => { | ||||
|       defineVariables[defineKey] = JSON.stringify(define[defineKey]); | ||||
|     }); | ||||
|     config | ||||
|       .plugin('DefinePlugin') | ||||
|       .tap((args) => [Object.assign(...args, defineVariables)]); | ||||
|   } | ||||
| }; | ||||
|  | @ -0,0 +1,9 @@ | |||
| const updateMiniCssLoaderPath = require('../utils/updateMiniCssLoaderPath'); | ||||
| 
 | ||||
| module.exports = (config, value, context) => { | ||||
|   const { command, userConfig } = context; | ||||
|   if (command === 'start') { | ||||
|     config.output.publicPath(value); | ||||
|     updateMiniCssLoaderPath(config, value, userConfig); | ||||
|   } | ||||
| }; | ||||
|  | @ -0,0 +1,12 @@ | |||
| const defaultConfig = require('../config/default.config'); | ||||
| 
 | ||||
| module.exports = (config, devServer, context) => { | ||||
|   const { userConfig } = context; | ||||
|   // make sure to use config proxy instead of config devServer.proxy
 | ||||
|   if (userConfig.proxy && devServer.proxy) { | ||||
|     console.log('use config proxy instead of devServer.proxy'); | ||||
|     delete devServer.proxy; | ||||
|   } | ||||
|   // merge default devServer
 | ||||
|   config.merge({ devServer: { ...defaultConfig.devServer, ...devServer } }); | ||||
| }; | ||||
|  | @ -0,0 +1,119 @@ | |||
| const path = require('path'); | ||||
| const fse = require('fs-extra'); | ||||
| const AddAssetHtmlPlugin = require('add-asset-html-webpack-plugin'); | ||||
| const Config = require('webpack-chain'); | ||||
| const formatWebpackMessages = require('react-dev-utils/formatWebpackMessages'); | ||||
| 
 | ||||
| module.exports = async(chainConfig, dll, { userConfig, rootDir, pkg, webpack }) => { | ||||
|   if (!dll) return; | ||||
| 
 | ||||
|   const dllPath = path.join(rootDir, 'dll'); | ||||
| 
 | ||||
|   let entry = {}; | ||||
|   const { dllEntry } = userConfig; | ||||
|   if (Object.keys(dllEntry).length !== 0) { | ||||
|     entry = dllEntry; | ||||
|   } else { | ||||
|     entry = { | ||||
|       vendor: Object.keys(pkg.dependencies) | ||||
|     }; | ||||
|   } | ||||
| 
 | ||||
|   const rebuildNeeded = rebuildCheck(entry, dllPath); | ||||
| 
 | ||||
|   if (rebuildNeeded) { | ||||
|     await buildDll(dllPath, entry, webpack, rootDir); | ||||
|   }; | ||||
| 
 | ||||
|   withDll(chainConfig, dllPath, entry, webpack); | ||||
| }; | ||||
| 
 | ||||
| function rebuildCheck(curEntry, prevEntryPath) { | ||||
|   const pkgPath = path.join(prevEntryPath, 'dll-pkg.json'); | ||||
|   if (!fse.pathExistsSync(pkgPath)) { | ||||
|     fse.ensureDirSync(prevEntryPath); | ||||
|     fse.emptyDirSync(prevEntryPath); | ||||
|     fse.writeFileSync(pkgPath, JSON.stringify(curEntry)); | ||||
|     return true; | ||||
|   } else { | ||||
|     const prevEntryString = fse.readFileSync(pkgPath, 'utf-8'); | ||||
|     if (prevEntryString === JSON.stringify(curEntry)) { | ||||
|       return false; | ||||
|     } | ||||
|     fse.emptyDirSync(prevEntryPath); | ||||
|     fse.writeFileSync(pkgPath, JSON.stringify(curEntry)); | ||||
|     return true; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| async function buildDll(dllPath, entry, webpack, rootDir) { | ||||
|   const chainConfig = new Config(); | ||||
|   chainConfig.entryPoints.clear(); | ||||
|   const entryKeys = Object.keys(entry); | ||||
|   entryKeys.forEach(entryKey => { | ||||
|     const entryValues = entry[entryKey]; | ||||
|     const escapedEntryKey = escapeStr(entryKey); | ||||
|     entryValues.forEach(entryVal => { | ||||
|       chainConfig.entry(escapedEntryKey).add(entryVal); | ||||
|     }); | ||||
|   }); | ||||
| 
 | ||||
|   chainConfig.output | ||||
|     .path(dllPath) | ||||
|     .library('_dll_[name]') | ||||
|     .filename('[name].dll.js'); | ||||
| 
 | ||||
|   chainConfig.plugin('dllPlugin').use(webpack.DllPlugin, [{ | ||||
|     name: '_dll_[name]', | ||||
|     path: path.join(dllPath, '[name].manifest.json') | ||||
|   }]); | ||||
| 
 | ||||
|   chainConfig.name('dll'); | ||||
| 
 | ||||
|   chainConfig.resolve.modules | ||||
|     .add('node_modules') | ||||
|     .add(path.resolve(rootDir, 'node_modules')); | ||||
| 
 | ||||
|   return new Promise((resolve, reject) => { | ||||
|     webpack(chainConfig.toConfig(), (err, stats) => { | ||||
|       if (err) { | ||||
|         reject(err); | ||||
|       } | ||||
| 
 | ||||
|       const info = stats.toJson({ | ||||
|         all: false, | ||||
|         errors: true, | ||||
|         warnings: true, | ||||
|         timings: true, | ||||
|       }); | ||||
| 
 | ||||
|       const messages = formatWebpackMessages(info); | ||||
| 
 | ||||
|       if (messages.errors.length) { | ||||
|         fse.ensureDirSync(dllPath); | ||||
|         fse.emptyDirSync(dllPath); | ||||
|         reject(messages.errors.join('')); | ||||
|       } | ||||
| 
 | ||||
|       resolve(); | ||||
|     }); | ||||
|   }); | ||||
| } | ||||
| 
 | ||||
| function withDll(chainConfig, dllPath, entry, webpack) { | ||||
|   const entryKeys = Object.keys(entry); | ||||
|   entryKeys.forEach(entryKey => { | ||||
|     const escapedEntryKey = escapeStr(entryKey); | ||||
|     chainConfig.plugin(`DllReferencePlugin_${entryKey}`).use(webpack.DllReferencePlugin, [{ | ||||
|       manifest: path.resolve(dllPath, `${escapedEntryKey}.manifest.json`) | ||||
|     }]); | ||||
|   }); | ||||
| 
 | ||||
|   chainConfig.plugin('AddAssetHtmlPlugin').use(AddAssetHtmlPlugin, [{ | ||||
|     filepath: path.resolve(dllPath, '*.dll.js') | ||||
|   }]).after('HtmlWebpackPlugin'); | ||||
| } | ||||
| 
 | ||||
| function escapeStr(str) { | ||||
|   return Buffer.from(str, 'utf8').toString('hex'); | ||||
| } | ||||
|  | @ -0,0 +1 @@ | |||
| module.exports = () => {}; | ||||
|  | @ -0,0 +1,27 @@ | |||
| const path = require('path'); | ||||
| 
 | ||||
| module.exports = (config, eslint, { rootDir }) => { | ||||
|   if (typeof eslint === 'boolean' && eslint === false) { | ||||
|     return config; | ||||
|   } | ||||
|   const { disable, ...args } = eslint; | ||||
|   if (!disable) { | ||||
|     const appSrc = path.join(rootDir, 'src'); | ||||
|     config.module | ||||
|       .rule('eslint') | ||||
|       .test(/\.(jsx?|tsx?)$/) | ||||
|       .include | ||||
|       .add(appSrc) | ||||
|       .end() | ||||
|       .enforce('pre') | ||||
|       .use('eslint') | ||||
|       .loader(require.resolve('eslint-loader')) | ||||
|       .tap((options) => ({ | ||||
|         cache: true, | ||||
|         eslintPath: require.resolve('eslint'), | ||||
|         formatter: require.resolve('react-dev-utils/eslintFormatter'), | ||||
|         ...options, | ||||
|         ...args | ||||
|       })); | ||||
|   } | ||||
| }; | ||||
|  | @ -0,0 +1,8 @@ | |||
| module.exports = (config, extensions) => { | ||||
|   if (Array.isArray(extensions)) { | ||||
|     extensions.forEach((extension) => { | ||||
|       config.resolve.extensions | ||||
|         .add(extension); | ||||
|     }); | ||||
|   } | ||||
| }; | ||||
|  | @ -0,0 +1,3 @@ | |||
| module.exports = (config, value) => { | ||||
|   config.merge({ externals: value }); | ||||
| }; | ||||
|  | @ -0,0 +1,5 @@ | |||
| module.exports = (config, filename) => { | ||||
|   if (filename) { | ||||
|     config.output.filename(filename); | ||||
|   } | ||||
| }; | ||||
|  | @ -0,0 +1,22 @@ | |||
| const path = require('path'); | ||||
| const formatWinPath = require('../utils/formatWinPath'); | ||||
| 
 | ||||
| module.exports = (config, hash, context) => { | ||||
|   const { command } = context; | ||||
|   // default is false
 | ||||
|   if (hash) { | ||||
|     // can not use [chunkhash] or [contenthash] for chunk in dev mode
 | ||||
|     const hashStr = typeof hash === 'boolean' || command === 'start' ? 'hash:6' : hash; | ||||
|     const fileName = config.output.get('filename'); | ||||
|     let pathArray = fileName.split('/'); | ||||
|     pathArray.pop(); // pop filename
 | ||||
|     pathArray = pathArray.filter((v) => v); | ||||
|     const outputPath = pathArray.length ? pathArray.join('/') : ''; | ||||
|     config.output.filename(formatWinPath(path.join(outputPath, `[name].[${hashStr}].js`))); | ||||
|     if (config.plugins.get('MiniCssExtractPlugin')) { | ||||
|       config.plugin('MiniCssExtractPlugin').tap((args) => [Object.assign(...args, { | ||||
|         filename: formatWinPath(path.join(outputPath, `[name].[${hashStr}].css`)), | ||||
|       })]); | ||||
|     } | ||||
|   } | ||||
| }; | ||||
|  | @ -0,0 +1,60 @@ | |||
| const formatWinPath = require('../utils/formatWinPath'); | ||||
| const addBablePlugins = require('./babelPlugins'); | ||||
| 
 | ||||
| module.exports = (config, injectBabel, context) => { | ||||
|   const { userConfig: { targets = [] } } = context; | ||||
|   if (targets.includes('miniapp') || targets.includes('wechat-miniprogram')) { | ||||
|     return; | ||||
|   } | ||||
|   if (injectBabel === 'runtime') { | ||||
|     ['jsx', 'tsx'].forEach((rule) => { | ||||
|       config.module | ||||
|         .rule(rule) | ||||
|         .use('babel-loader') | ||||
|         .tap((options) => { | ||||
|           // get @babel/plugin-transform-runtime
 | ||||
|           const babelPlugins = options.plugins || []; | ||||
|           const targetPlugin = formatWinPath('@babel/plugin-transform-runtime'); | ||||
|           const plguinOption = { | ||||
|             corejs: false, | ||||
|             helpers: true, | ||||
|             regenerator: true, | ||||
|             useESModules: false, | ||||
|           }; | ||||
|           const plugins = babelPlugins.map((plugin) => { | ||||
|             if (typeof plugin === 'string' && formatWinPath(plugin).indexOf(targetPlugin) > -1 | ||||
|               || Array.isArray(plugin) && formatWinPath(plugin[0]).indexOf(targetPlugin) > -1 ) { | ||||
|               return [Array.isArray(plugin) ? plugin[0] : plugin, plguinOption]; | ||||
|             } else { | ||||
|               return [require.resolve('@babel/plugin-transform-runtime'), plguinOption]; | ||||
|             } | ||||
|           }); | ||||
|           return Object.assign(options, { plugins }); | ||||
|         }); | ||||
|     }); | ||||
|   } else if (injectBabel === 'polyfill') { | ||||
|     const entries = config.toConfig().entry; | ||||
|     const rule = config.module.rule('polyfill').test(/\.jsx?|\.tsx?$/); | ||||
|     const fileList = []; | ||||
|     if (!entries) return; | ||||
|     Object.keys(entries).forEach((key) => { | ||||
|       let addPolyfill = false; | ||||
|       // only include entry path
 | ||||
|       for (let i = 0; i < entries[key].length; i += 1) { | ||||
|         // filter node_modules file add by plugin
 | ||||
|         if (!/node_modules/.test(entries[key][i])) { | ||||
|           rule.include.add(entries[key][i]); | ||||
|           fileList.push(entries[key][i]); | ||||
|           addPolyfill = true; | ||||
|           break; | ||||
|         } | ||||
|       } | ||||
|       if (!addPolyfill) { | ||||
|         rule.include.add(entries[key][0]); | ||||
|         fileList.push(entries[key][0]); | ||||
|       } | ||||
|     }); | ||||
|     rule.use('polyfill-loader').loader(require.resolve('../utils/polyfillLoader')).options({}); | ||||
|     addBablePlugins(config, [[require.resolve('../utils/babelPluginCorejsLock.js'), { fileList }]]); | ||||
|   } | ||||
| }; | ||||
|  | @ -0,0 +1,105 @@ | |||
| const { resolve } = require('path'); | ||||
| const MiniCssExtractPlugin = require('mini-css-extract-plugin'); | ||||
| const { WEB, WEEX, DOCUMENT, KRAKEN, MINIAPP, WECHAT_MINIPROGRAM } = require('../constants'); | ||||
| 
 | ||||
| const configPath = resolve(__dirname, '../'); | ||||
| 
 | ||||
| const webStandardList = [ | ||||
|   WEB, | ||||
| ]; | ||||
| 
 | ||||
| const inlineStandardList = [ | ||||
|   WEEX, KRAKEN, | ||||
| ]; | ||||
| 
 | ||||
| const miniappStandardList = [ | ||||
|   MINIAPP, | ||||
|   WECHAT_MINIPROGRAM, | ||||
| ]; | ||||
| 
 | ||||
| module.exports = (config, value, context) => { | ||||
|   const { taskName, command } = context; | ||||
|   const isDev = command === 'start'; | ||||
| 
 | ||||
|   const cssRule = config.module.rule('css'); | ||||
|   const cssModuleRule = config.module.rule('css-module'); | ||||
|   setCSSRule(cssRule, context, value); | ||||
|   setCSSRule(cssModuleRule, context, value); | ||||
| 
 | ||||
|   const lessRule = config.module.rule('less'); | ||||
|   const lessModuleRule = config.module.rule('less-module'); | ||||
|   setCSSRule(lessRule, context, value); | ||||
|   setCSSRule(lessModuleRule, context, value); | ||||
| 
 | ||||
|   const sassRule = config.module.rule('scss'); | ||||
|   const sassModuleRule = config.module.rule('scss-module'); | ||||
|   setCSSRule(sassRule, context, value); | ||||
|   setCSSRule(sassModuleRule, context, value); | ||||
|   if ((webStandardList.includes(taskName) || miniappStandardList.includes(taskName)) && !value) { | ||||
|     config.plugin('MiniCssExtractPlugin') | ||||
|       .use(MiniCssExtractPlugin, [{ | ||||
|         filename: isDev ? `${taskName}/[name].css`: '[name].css', | ||||
|         ignoreOrder: true | ||||
|       }]); | ||||
|   } | ||||
| }; | ||||
| 
 | ||||
| function setCSSRule(configRule, context, value) { | ||||
|   const { taskName } = context; | ||||
|   const isInlineStandard = inlineStandardList.includes(taskName); | ||||
|   const isWebStandard = webStandardList.includes(taskName); | ||||
|   const isMiniAppStandard = miniappStandardList.includes(taskName); | ||||
|   const isNodeStandard = taskName === DOCUMENT; | ||||
| 
 | ||||
|   // When taskName is weex or kraken, inlineStyle should be true
 | ||||
|   if (isInlineStandard) { | ||||
|     value = true; | ||||
|   } | ||||
| 
 | ||||
|   if (value) { | ||||
|     configRule.uses.delete('MiniCssExtractPlugin.loader'); | ||||
|     // enbale inlineStyle
 | ||||
|     if (isInlineStandard || isMiniAppStandard) { | ||||
|       configInlineStyle(configRule) | ||||
|         .use('postcss-loader') | ||||
|         .tap(getPostCssConfig.bind(null, 'normal')); | ||||
|     } else { | ||||
|       configInlineStyle(configRule) | ||||
|         .use('postcss-loader') | ||||
|         .tap(getPostCssConfig.bind(null, 'web-inline')); | ||||
|     } | ||||
| 
 | ||||
|   } else if (isWebStandard || isMiniAppStandard) { | ||||
|       configRule | ||||
|         .use('postcss-loader') | ||||
|         .tap(getPostCssConfig.bind(null, isWebStandard ? 'web' : 'normal')) | ||||
|         .end(); | ||||
|     } else if (isNodeStandard) { | ||||
|       // Do not generate CSS file, it will be built by web complier
 | ||||
|       configRule | ||||
|         .use('null-loader') | ||||
|         .loader(require.resolve('null-loader')) | ||||
|         .end(); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| function configInlineStyle(configRule) { | ||||
|   return configRule | ||||
|     .use('css-loader') | ||||
|     .loader(require.resolve('stylesheet-loader')) | ||||
|     .options({ | ||||
|       transformDescendantCombinator: true, | ||||
|     }).end(); | ||||
| } | ||||
| 
 | ||||
| function getPostCssConfig(type, options) { | ||||
|   return { | ||||
|     ...options, | ||||
|     config: { | ||||
|       path: configPath, | ||||
|       ctx: { | ||||
|         type | ||||
|       }, | ||||
|     } | ||||
|   }; | ||||
| } | ||||
|  | @ -0,0 +1,18 @@ | |||
| module.exports = (config, lessLoaderOptions) => { | ||||
|   if (lessLoaderOptions) { | ||||
|     [ | ||||
|       'less', | ||||
|       'less-module', | ||||
|     ].forEach(rule => { | ||||
|       if (config.module.rules.get(rule)) { | ||||
|         config.module | ||||
|           .rule(rule) | ||||
|           .use('less-loader') | ||||
|           .tap((options) => ({ | ||||
|             ...options, | ||||
|             ...lessLoaderOptions, | ||||
|           })); | ||||
|       } | ||||
|     }); | ||||
|   } | ||||
| }; | ||||
|  | @ -0,0 +1,5 @@ | |||
| module.exports = (config, library) => { | ||||
|   if (library) { | ||||
|     config.output.library(library); | ||||
|   } | ||||
| }; | ||||
|  | @ -0,0 +1,5 @@ | |||
| module.exports = (config, libraryExport) => { | ||||
|   if (libraryExport) { | ||||
|     config.output.libraryExport(libraryExport); | ||||
|   } | ||||
| }; | ||||
|  | @ -0,0 +1,5 @@ | |||
| module.exports = (config, libraryTarget) => { | ||||
|   if (libraryTarget) { | ||||
|     config.output.libraryTarget(libraryTarget); | ||||
|   } | ||||
| }; | ||||
|  | @ -0,0 +1,6 @@ | |||
| module.exports = (config, value, context) => { | ||||
|   const { command } = context; | ||||
|   // minify always be false in dev mode
 | ||||
|   const minify = command === 'start' ? false : value; | ||||
|   config.optimization.minimize(minify); | ||||
| }; | ||||
|  | @ -0,0 +1,18 @@ | |||
| const webpackDevMock = require('webpack-dev-mock'); | ||||
| 
 | ||||
| module.exports = (config, mock, context) => { | ||||
|   // dev mock
 | ||||
|   const { commandArgs, command } = context; | ||||
|   if (!commandArgs.disableMock && command === 'start' && mock) { | ||||
|     const originalDevServeBefore = config.devServer.get('before'); | ||||
|     // replace devServer before function
 | ||||
|     config.merge({ devServer: { | ||||
|       before(app, server) { | ||||
|         webpackDevMock(app); | ||||
|         if (typeof originalDevServeBefore === 'function') { | ||||
|           originalDevServeBefore(app, server); | ||||
|         } | ||||
|       }, | ||||
|     }}); | ||||
|   } | ||||
| }; | ||||
|  | @ -0,0 +1,8 @@ | |||
| module.exports = (config, modules) => { | ||||
|   if (Array.isArray(modules)) { | ||||
|     modules.forEach((module) => { | ||||
|       config.resolve.modules | ||||
|         .add(module); | ||||
|     }); | ||||
|   } | ||||
| }; | ||||
|  | @ -0,0 +1,18 @@ | |||
| const path = require('path'); | ||||
| const { last } = require('lodash'); | ||||
| const formatWinPath = require('../utils/formatWinPath'); | ||||
| 
 | ||||
| function getFilename(filePath) { | ||||
|   return last((filePath || '').split('/')); | ||||
| } | ||||
| module.exports = (config, outputAssetsPath) => { | ||||
|   const filename = getFilename(config.output.get('filename')); | ||||
|   config.output.filename(formatWinPath(path.join(outputAssetsPath.js || '', filename))); | ||||
| 
 | ||||
|   if (config.plugins.get('MiniCssExtractPlugin')) { | ||||
|     const options = config.plugin('MiniCssExtractPlugin').get('args')[0]; | ||||
|     config.plugin('MiniCssExtractPlugin').tap((args) => [Object.assign(...args, { | ||||
|       filename: formatWinPath(path.join(outputAssetsPath.css || '', getFilename(options.filename))), | ||||
|     })]); | ||||
|   } | ||||
| }; | ||||
|  | @ -0,0 +1,18 @@ | |||
| const path = require('path'); | ||||
| 
 | ||||
| module.exports = (config, outputDir, context) => { | ||||
|   const { rootDir } = context; | ||||
| 
 | ||||
|   // outputPath:build/*
 | ||||
|   const outputPath = path.resolve(rootDir, outputDir); | ||||
| 
 | ||||
|   config.output.path(outputPath); | ||||
|   // copy public folder to outputDir
 | ||||
|   // copy-webpack-plugin patterns must be an array
 | ||||
|   if (config.plugins.get('CopyWebpackPlugin')) { | ||||
|     config.plugin('CopyWebpackPlugin').tap(([args]) => [[{ | ||||
|       ...(args[0] || {}), | ||||
|       to: outputPath, | ||||
|     }]]); | ||||
|   } | ||||
| }; | ||||
|  | @ -0,0 +1,20 @@ | |||
| module.exports = (config, postcssrc) => { | ||||
|   if (postcssrc) { | ||||
|     // remove postcss-loader options, use postcss config file
 | ||||
|     [ | ||||
|       'scss', | ||||
|       'scss-module', | ||||
|       'css', | ||||
|       'css-module', | ||||
|       'less', | ||||
|       'less-module', | ||||
|     ].forEach(rule => { | ||||
|       if (config.module.rules.has(rule)) { | ||||
|         config.module | ||||
|           .rule(rule) | ||||
|           .use('postcss-loader') | ||||
|           .tap(() => ({})); | ||||
|       } | ||||
|     }); | ||||
|   } | ||||
| }; | ||||
|  | @ -0,0 +1,40 @@ | |||
| const merge = require('lodash/merge'); | ||||
| 
 | ||||
| module.exports = (config, proxyConfig) => { | ||||
|   const proxyRules = Object.entries(proxyConfig); | ||||
|   if (proxyRules.length) { | ||||
|     const proxy = proxyRules.map(([match, opts]) => { | ||||
|       // set enable false to disable proxy rule
 | ||||
|       const { enable, target, ...proxyRule } = opts; | ||||
|       if (enable !== false) { | ||||
|         return merge({ | ||||
|           target, | ||||
|           changeOrigin: true, | ||||
|           logLevel: 'warn', | ||||
|           onProxyRes: function onProxyReq(proxyRes, req) { | ||||
|             proxyRes.headers['x-proxy-by'] = 'ice-proxy'; | ||||
|             proxyRes.headers['x-proxy-match'] = match; | ||||
|             proxyRes.headers['x-proxy-target'] = target; | ||||
| 
 | ||||
|             let distTarget = target; | ||||
|             if (target && target.endsWith('/')) { | ||||
|               distTarget = target.replace(/\/$/, ''); | ||||
|             } | ||||
|             proxyRes.headers['x-proxy-target-path'] = distTarget + req.url; | ||||
|           }, | ||||
|           onError: function onError(err, req, res) { | ||||
|             // proxy server error can't trigger onProxyRes
 | ||||
|             res.writeHead(500, { | ||||
|               'x-proxy-by': 'ice-proxy', | ||||
|               'x-proxy-match': match, | ||||
|               'x-proxy-target': target, | ||||
|             }); | ||||
|             res.end(`proxy server error: ${err.message}`); | ||||
|           }, | ||||
|         }, proxyRule, { context: match }); | ||||
|       } | ||||
|       return false; | ||||
|     }).filter((v) => v); | ||||
|     config.devServer.proxy(proxy); | ||||
|   } | ||||
| }; | ||||
|  | @ -0,0 +1,9 @@ | |||
| const updateMiniCssLoaderPath = require('../utils/updateMiniCssLoaderPath'); | ||||
| 
 | ||||
| module.exports = (config, value, context) => { | ||||
|   const { command, userConfig } = context; | ||||
|   if (command === 'build') { | ||||
|     config.output.publicPath(value); | ||||
|     updateMiniCssLoaderPath(config, value, userConfig); | ||||
|   } | ||||
| }; | ||||
|  | @ -0,0 +1,18 @@ | |||
| module.exports = (config, sassLoaderOptions) => { | ||||
|   if (sassLoaderOptions) { | ||||
|     [ | ||||
|       'scss', | ||||
|       'scss-module', | ||||
|     ].forEach(rule => { | ||||
|       if (config.module.rules.get(rule)) { | ||||
|         config.module | ||||
|           .rule(rule) | ||||
|           .use('sass-loader') | ||||
|           .tap((options) => ({ | ||||
|             ...options, | ||||
|             ...sassLoaderOptions, | ||||
|           })); | ||||
|       } | ||||
|     }); | ||||
|   } | ||||
| }; | ||||
|  | @ -0,0 +1,11 @@ | |||
| module.exports = (config, sourceMap, context) => { | ||||
|   const { command } = context; | ||||
|   if (command === 'build' && sourceMap) { | ||||
|     config.devtool('source-map'); | ||||
|     config.optimization | ||||
|       .minimizer('TerserPlugin') | ||||
|       .tap(([options]) => [ | ||||
|         { ...options, sourceMap: true }, | ||||
|       ]); | ||||
|   } | ||||
| }; | ||||
|  | @ -0,0 +1,11 @@ | |||
| module.exports = (config, terserOptions, context) => { | ||||
|   const { command } = context; | ||||
|   if (command === 'build' && terserOptions && config.optimization.minimizers.get('TerserPlugin')) { | ||||
|     config.optimization.minimizer('TerserPlugin').tap(([options]) => [ | ||||
|       { | ||||
|         ...options, | ||||
|         ...terserOptions, | ||||
|       }, | ||||
|     ]); | ||||
|   } | ||||
| }; | ||||
|  | @ -0,0 +1,8 @@ | |||
| const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin'); | ||||
| 
 | ||||
| module.exports = (config, tsChecker) => { | ||||
|   if (tsChecker) { | ||||
|     config.plugin('fork-ts-checker-webpack-plugin') | ||||
|       .use(ForkTsCheckerWebpackPlugin); | ||||
|   } | ||||
| }; | ||||
|  | @ -0,0 +1,14 @@ | |||
| module.exports = (config, vendor) => { | ||||
|   if (!vendor) { | ||||
|     config.optimization.splitChunks({ cacheGroups: {} }); | ||||
|   } else { | ||||
|     config.optimization.splitChunks({ cacheGroups: { | ||||
|       vendor: { | ||||
|         test: /[\\/]node_modules[\\/]/, | ||||
|         name: 'vendor', | ||||
|         chunks: 'initial', | ||||
|         minChunks: 2, | ||||
|       }, | ||||
|     } }); | ||||
|   } | ||||
| }; | ||||
|  | @ -0,0 +1,23 @@ | |||
| const path = require('path'); | ||||
| 
 | ||||
| const coreJSPath = path.dirname(require.resolve('core-js/package.json')); | ||||
| // eslint-disable-next-line no-unused-vars
 | ||||
| module.exports = ({ types }, { fileList }) => { | ||||
|   return { | ||||
|     visitor: { | ||||
|       ImportDeclaration(nodePath, state) { | ||||
|         const entryFile = fileList.find((filePath) => { | ||||
|           // filePath may not have an extension
 | ||||
|           return filePath.includes((state.filename || '').replace(/\.[^/.]+$/, '')); | ||||
|         }); | ||||
|         if (entryFile) { | ||||
|           const { node } = nodePath; | ||||
|           // only replace core-js/modules/xxx added by @babel/preset-env
 | ||||
|           if (node.source.value.startsWith('core-js/modules')) { | ||||
|             node.source.value = node.source.value.replace('core-js/', `${coreJSPath}/`); | ||||
|           } | ||||
|         } | ||||
|       }, | ||||
|     }, | ||||
|   }; | ||||
| }; | ||||
|  | @ -0,0 +1,5 @@ | |||
| module.exports = (outputPath) => { | ||||
|   const isWin = process.platform === 'win32'; | ||||
|   // js\index.js => js/index.js
 | ||||
|   return isWin ? outputPath.replace(/\\/g, '/') : outputPath; | ||||
| }; | ||||
|  | @ -0,0 +1,50 @@ | |||
| const mkcert = require('mkcert'); | ||||
| const fs = require('fs-extra'); | ||||
| const path = require('path'); | ||||
| 
 | ||||
| const rootDirPath = path.resolve(__dirname, '../Rax_CA'); | ||||
| 
 | ||||
| function generateCA() { | ||||
|   return new Promise((resolve, reject) => { | ||||
|     mkcert.createCA({ | ||||
|       organization: 'Rax Team', | ||||
|       countryCode: 'CN', | ||||
|       state: 'ZJ', | ||||
|       locality: 'HZ', | ||||
|       validityDays: 3650, | ||||
|     }).then((ca) => { | ||||
|       if (!fs.existsSync(rootDirPath)) { | ||||
|         // create Rax_CA folder if not exists
 | ||||
|         fs.mkdirSync(rootDirPath); | ||||
|       } | ||||
|       const keyPath = path.join(rootDirPath, 'rootCa.key'); | ||||
|       const certPath = path.join(rootDirPath, 'rootCa.crt'); | ||||
|       fs.writeFileSync(keyPath, ca.key); | ||||
|       fs.writeFileSync(certPath, ca.cert); | ||||
|       resolve({ | ||||
|         key: keyPath, | ||||
|         cert: certPath, | ||||
|       }); | ||||
|     }).catch((err) => { | ||||
|       reject(err); | ||||
|     }); | ||||
|   }); | ||||
| } | ||||
| 
 | ||||
| module.exports = async function getCertificate() { | ||||
|   const certPath = path.join(rootDirPath, 'rootCa.crt'); | ||||
|   const keyPath = path.join(rootDirPath, 'rootCa.key'); | ||||
|   if (!fs.existsSync(certPath) || !fs.existsSync(keyPath)) { | ||||
|     await generateCA(); | ||||
|   } | ||||
|   console.log('当前使用的 HTTPS 证书路径(如有需要请手动信任此文件)'); | ||||
|   console.log('   ', certPath); | ||||
|   return new Promise((resolve, reject) => { | ||||
|     mkcert.createCert({ | ||||
|       domains: ['127.0.0.1', 'localhost'], | ||||
|       validityDays: 365, | ||||
|       caKey: fs.readFileSync(keyPath), | ||||
|       caCert: fs.readFileSync(certPath), | ||||
|     }).then(resolve).catch(reject); | ||||
|   }); | ||||
| }; | ||||
|  | @ -0,0 +1,27 @@ | |||
| const path = require('path'); | ||||
| const fs = require('fs-extra'); | ||||
| 
 | ||||
| module.exports = (route, rootDir) => { | ||||
|   if (route.name) { | ||||
|     return route.name; | ||||
|   } | ||||
| 
 | ||||
|   const appConfig = fs.readJsonSync(path.resolve(rootDir, 'src/app.json')); | ||||
| 
 | ||||
|   const routeName = appConfig.routeName ? appConfig.routeName : 'path'; | ||||
| 
 | ||||
|   if (routeName === 'path') { | ||||
|     return route.source.replace(/\//g, '_'); | ||||
|   } | ||||
| 
 | ||||
|   if (routeName === 'pages') { | ||||
|     try { | ||||
|       // get Home from pages/Home/index or pages/Home
 | ||||
|       const name = route.source.match(/pages\/([^/]*)/); | ||||
|       return name[1]; | ||||
|     } catch (e) { | ||||
|       console.error('"routeName": "pages" mode request routes in /pages directory'); | ||||
|       process.exit(1); | ||||
|     } | ||||
|   } | ||||
| }; | ||||
|  | @ -0,0 +1,7 @@ | |||
| module.exports = (content) => { | ||||
|   return ` | ||||
| import "core-js/stable"; | ||||
| import "regenerator-runtime/runtime"; | ||||
| ${content} | ||||
|   `;
 | ||||
| }; | ||||
|  | @ -0,0 +1,17 @@ | |||
| module.exports = (config, value, userConfig) => { | ||||
|   const shouldUseRelativeAssetPaths = value === './'; | ||||
|   const outputCssPath = userConfig.outputAssetsPath && userConfig.outputAssetsPath.css; | ||||
|   // when publicPath is ./ assets in css will be resolve as ./assets/xxx
 | ||||
|   // the actual path where assets exist is ../assets/xxx when output css path is `css`
 | ||||
|   if (shouldUseRelativeAssetPaths && outputCssPath) { | ||||
|     const pathArray = outputCssPath.split('/').length; | ||||
|     const publicPath = `${[...Array(pathArray)].map(() => '..').join('/')}/`; | ||||
|     // MiniCssExtractPlugin.loader will use output.publicPath as default
 | ||||
|     ['scss', 'scss-module', 'css', 'css-module', 'less', 'less-module'].forEach((rule) => { | ||||
|       if (config.module.rules.get(rule)) { | ||||
|         config.module.rule(rule).use('MiniCssExtractPlugin.loader').tap(() => ({ publicPath })); | ||||
|       } | ||||
|     }); | ||||
|   } | ||||
| }; | ||||
| 
 | ||||
|  | @ -1,6 +1,6 @@ | |||
| { | ||||
|   "name": "build-plugin-app-core", | ||||
|   "version": "0.1.18", | ||||
|   "version": "0.1.19", | ||||
|   "description": "the core plugin for icejs and raxjs.", | ||||
|   "author": "ice-admin@alibaba-inc.com", | ||||
|   "homepage": "", | ||||
|  |  | |||
|  | @ -5,7 +5,7 @@ interface IProps { | |||
|   error: Error; | ||||
| } | ||||
| 
 | ||||
| const toTitle = (error: Error, componentStack: string): string => { | ||||
| const toTitle = (error: Error): string => { | ||||
|   return `${error.toString()}`; | ||||
| }; | ||||
| 
 | ||||
|  |  | |||
|  | @ -46,11 +46,7 @@ class ErrorBoundary extends Component<IProps, IState> { | |||
|     const { error } = this.state; | ||||
|     // render fallback UI if there is error | ||||
|     if (error !== null && typeof Fallback === 'function') { | ||||
|       return ( | ||||
|         <Fallback | ||||
|           error={error} | ||||
|         /> | ||||
|       ); | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     return children || null; | ||||
|  |  | |||
|  | @ -17,7 +17,7 @@ import createShareAPI, { history } from 'create-app-shared'; | |||
| 
 | ||||
| import loadRuntimeModules from './loadRuntimeModules'; | ||||
| import loadStaticModules from './loadStaticModules'; | ||||
| import staticConfig from './staticConfig'; | ||||
| import defaultStaticConfig from './staticConfig'; | ||||
| import { setAppConfig } from './appConfig'; | ||||
| import { mount, unmount } from './render'; | ||||
| import ErrorBoundary from './ErrorBoundary'; | ||||
|  | @ -46,7 +46,7 @@ const { | |||
|   withRouter: defaultWithRouter | ||||
| }, loadRuntimeModules); | ||||
| 
 | ||||
| export function runApp(appConfig) { | ||||
| export function runApp(appConfig, staticConfig?: any) { | ||||
|   let renderer; | ||||
|   <% if(isRax){ %> | ||||
|     renderer = raxAppRenderer; | ||||
|  | @ -59,7 +59,7 @@ export function runApp(appConfig) { | |||
|   <% } %> | ||||
|   renderer({ | ||||
|     appConfig, | ||||
|     staticConfig, | ||||
|     staticConfig: staticConfig || defaultStaticConfig, | ||||
|     setAppConfig, | ||||
|     createBaseApp, | ||||
|     createHistory, | ||||
|  |  | |||
|  | @ -116,6 +116,6 @@ function checkTargets(targets) { | |||
| 
 | ||||
| function matchTargets(targets) { | ||||
|   return targets.every(target => { | ||||
|     return ['web', 'miniapp', 'wechat-miniprogram'].includes(target); | ||||
|     return ['web', 'miniapp', 'wechat-miniprogram', 'weex', 'kraken', 'bytedance-microapp', 'quickapp'].includes(target); | ||||
|   }); | ||||
| } | ||||
|  |  | |||
|  | @ -0,0 +1 @@ | |||
| # plugin-ssr | ||||
|  | @ -0,0 +1,35 @@ | |||
| { | ||||
|   "name": "build-plugin-ice-ssr", | ||||
|   "version": "1.7.3", | ||||
|   "description": "ssr plugin", | ||||
|   "author": "ice-admin@alibaba-inc.com", | ||||
|   "homepage": "", | ||||
|   "license": "MIT", | ||||
|   "main": "lib/index.js", | ||||
|   "directories": { | ||||
|     "lib": "lib", | ||||
|     "test": "__tests__" | ||||
|   }, | ||||
|   "files": [ | ||||
|     "lib", | ||||
|     "src" | ||||
|   ], | ||||
|   "publishConfig": { | ||||
|     "registry": "http://registry.npmjs.com/" | ||||
|   }, | ||||
|   "repository": { | ||||
|     "type": "git", | ||||
|     "url": "git@github.com:alibaba/ice.git" | ||||
|   }, | ||||
|   "scripts": { | ||||
|     "test": "echo \"Error: run tests from root\" && exit 1" | ||||
|   }, | ||||
|   "dependencies": { | ||||
|     "build-scripts-config": "^0.1.6", | ||||
|     "chalk": "^4.0.0", | ||||
|     "cheerio": "^1.0.0-rc.3", | ||||
|     "ejs": "^3.0.1", | ||||
|     "fs-extra": "^8.1.0", | ||||
|     "html-minifier": "^4.0.0" | ||||
|   } | ||||
| } | ||||
|  | @ -0,0 +1,143 @@ | |||
| import * as path from 'path'; | ||||
| import * as fse from 'fs-extra'; | ||||
| import * as ejs from 'ejs'; | ||||
| import { minify } from 'html-minifier'; | ||||
| import { getWebpackConfig } from 'build-scripts-config'; | ||||
| 
 | ||||
| const plugin = async (api): Promise<void> => { | ||||
|   const { context, registerTask, getValue, onGetWebpackConfig, onHook, log } = api; | ||||
|   const { rootDir, command, webpack, userConfig, commandArgs } = context; | ||||
|   const TEMP_PATH = getValue('TEMP_PATH'); | ||||
|   const ssrEntry = path.join(TEMP_PATH, 'server.ts'); | ||||
|   // Note: Compatible plugins to modify configuration
 | ||||
|   const buildDir = path.join(rootDir, userConfig.outputDir); | ||||
|   const serverDir = path.join(buildDir, 'server'); | ||||
|   const serverFilename = 'index.js'; | ||||
| 
 | ||||
|   const templatePath = path.join(__dirname, '../src/server.ts.ejs'); | ||||
|   const templateContent = fse.readFileSync(templatePath, 'utf-8'); | ||||
|   const content = ejs.render(templateContent); | ||||
|   fse.ensureDirSync(path.dirname(ssrEntry)); | ||||
|   fse.writeFileSync(ssrEntry, content, 'utf-8'); | ||||
| 
 | ||||
|   const mode = command === 'start' ? 'development' : 'production'; | ||||
|   const webpackConfig = getWebpackConfig(mode); | ||||
|   // config DefinePlugin out of onGetWebpackConfig, so it can be modified by user config
 | ||||
|   webpackConfig | ||||
|     .plugin('DefinePlugin') | ||||
|     .use(webpack.DefinePlugin, [{ | ||||
|       'process.env.APP_MODE': JSON.stringify(commandArgs.mode || command), | ||||
|       'process.env.SERVER_PORT': JSON.stringify(commandArgs.port), | ||||
|     }]); | ||||
|   registerTask('ssr', webpackConfig); | ||||
|   onGetWebpackConfig('ssr', (config) => { | ||||
|     config.entryPoints.clear(); | ||||
| 
 | ||||
|     config.entry('server').add(ssrEntry); | ||||
| 
 | ||||
|     config.target('node'); | ||||
| 
 | ||||
|     config.name('ssr'); | ||||
| 
 | ||||
|     config.module | ||||
|       .rule('polyfill') | ||||
|       .include.add(ssrEntry); | ||||
| 
 | ||||
|     config | ||||
|       .plugin('DefinePlugin') | ||||
|       .tap(([args]) => [{ ...args, 'process.env.__IS_SERVER__': true }]); | ||||
| 
 | ||||
|     config.plugins.delete('MiniCssExtractPlugin'); | ||||
|     ['scss', 'scss-module', 'css', 'css-module', 'less', 'less-module'].forEach((rule) => { | ||||
|       if (config.module.rules.get(rule)) { | ||||
|         config.module.rule(rule).uses.delete('MiniCssExtractPlugin.loader'); | ||||
|         config.module | ||||
|           .rule(rule) | ||||
|           .use('css-loader') | ||||
|           .tap((options) => ({ | ||||
|             ...options, | ||||
|             onlyLocals: true | ||||
|           })); | ||||
|       } | ||||
|     }); | ||||
| 
 | ||||
|     config.output | ||||
|       .path(serverDir) | ||||
|       .filename(serverFilename) | ||||
|       .publicPath('/') | ||||
|       .libraryTarget('commonjs2'); | ||||
| 
 | ||||
|     // in case of app with client and server code, webpack-node-externals is helpful to reduce server bundle size
 | ||||
|     // while by bundle all dependencies, developers do not need to concern about the dependencies of server-side
 | ||||
|     // TODO: support options to enable nodeExternals
 | ||||
|     // empty externals added by config external
 | ||||
|     config.externals([]); | ||||
| 
 | ||||
|     async function serverRender(res, req) { | ||||
|       const htmlTemplate = fse.readFileSync(path.join(buildDir, 'index.html'), 'utf8'); | ||||
|       console.log('[SSR]', 'start server render'); | ||||
|       const requirePath = path.join(serverDir, serverFilename); | ||||
|       delete require.cache[requirePath]; | ||||
|       // eslint-disable-next-line
 | ||||
|       const serverRender = require(requirePath) | ||||
|       const { html, error } = await serverRender.default({ pathname: req.path, htmlTemplate }); | ||||
|       if (error) { | ||||
|         log.error('[SSR] Server side rendering error, downgraded to client side rendering'); | ||||
|         log.error(error); | ||||
|       } | ||||
|       console.log('[SSR]', `output html content\n${html}\n`); | ||||
|       res.send(html); | ||||
|     } | ||||
|     if (command === 'start') { | ||||
|       config.devServer | ||||
|         .hot(true) | ||||
|         .writeToDisk((filePath) => { | ||||
|           return /(server\/index\.js|index.html)$/.test(filePath); | ||||
|         }); | ||||
|       let serverReady = false; | ||||
|       let httpResponseQueue = []; | ||||
|       const originalDevServeBefore = config.devServer.get('before'); | ||||
|       config.devServer.set('before', (app, server) => { | ||||
|         if (typeof originalDevServeBefore === 'function') { | ||||
|           originalDevServeBefore(app, server); | ||||
|         } | ||||
|         let compilerDoneCount = 0; | ||||
|         server.compiler.compilers.forEach((compiler) => { | ||||
|           compiler.hooks.done.tap('ssrServer', () => { | ||||
|             compilerDoneCount++; | ||||
|             // wait until all compiler is done
 | ||||
|             if (compilerDoneCount === server.compiler.compilers.length) { | ||||
|               serverReady = true; | ||||
|               httpResponseQueue.forEach(([req, res]) => { | ||||
|                 serverRender(res, req); | ||||
|               }); | ||||
|               // empty httpResponseQueue
 | ||||
|               httpResponseQueue = []; | ||||
|             } | ||||
|           }); | ||||
|         }); | ||||
| 
 | ||||
|         const pattern = /^\/?((?!\.(js|css|map|json|png|jpg|jpeg|gif|svg|eot|woff2|ttf|ico)).)*$/; | ||||
|         app.get(pattern, async (req, res) => { | ||||
|           if (serverReady) { | ||||
|             serverRender(res, req); | ||||
|           } else { | ||||
|             httpResponseQueue.push([req, res]); | ||||
|           } | ||||
|         }); | ||||
|       }); | ||||
|     } | ||||
|   }); | ||||
| 
 | ||||
|   onHook(`after.${command}.compile`, () => { | ||||
|     const serverFilePath = path.join(serverDir, serverFilename); | ||||
|     const htmlFilePath = path.join(buildDir, 'index.html'); | ||||
|     const bundle = fse.readFileSync(serverFilePath, 'utf-8'); | ||||
|     const html = fse.readFileSync(htmlFilePath, 'utf-8'); | ||||
|     const minifedHtml = minify(html, { collapseWhitespace: true, quoteCharacter: '\'' }); | ||||
|     const newBundle = bundle.replace(/__ICE_SERVER_HTML_TEMPLATE__/, minifedHtml); | ||||
|     fse.writeFileSync(serverFilePath, newBundle, 'utf-8'); | ||||
|   }); | ||||
| }; | ||||
| 
 | ||||
| export default plugin; | ||||
|  | @ -0,0 +1,8 @@ | |||
| { | ||||
|   "extends": "../../tsconfig.settings.json", | ||||
|   "compilerOptions": { | ||||
|     "baseUrl": "./", | ||||
|     "rootDir": "src", | ||||
|     "outDir": "lib" | ||||
|   }, | ||||
| } | ||||
|  | @ -11,14 +11,16 @@ module.exports = (api) => { | |||
|     if (target === 'miniapp' || target === 'wechat-miniprogram') { | ||||
|       onGetWebpackConfig(target, (config) => { | ||||
|         const projectType = getValue('PROJECT_TYPE'); | ||||
|         const { outputDir = 'build' } = userConfig; | ||||
|         // Clear entry
 | ||||
|         config.entryPoints.clear(); | ||||
|         // App entry
 | ||||
|         config.entry('index').add(builderShared.pathHelper.getDepPath(rootDir, `app.${projectType}`)); | ||||
| 
 | ||||
|         config.output.path(path.join(rootDir, 'build')); | ||||
|         const outputPath = path.resolve(rootDir, outputDir, target); | ||||
|         config.output.path(path.join(rootDir, 'build', target)); | ||||
| 
 | ||||
|         miniappConfig.setConfig(config, userConfig[target] || {}, { context, target, babelRuleName: 'babel-loader', onGetWebpackConfig }); | ||||
|         miniappConfig.setConfig(config, userConfig[target] || {}, { context, target, babelRuleName: 'babel-loader', outputPath }); | ||||
| 
 | ||||
|         if (config.plugins.get('MiniCssExtractPlugin')) { | ||||
|           config.plugin('MiniCssExtractPlugin').tap((args) => [ | ||||
|  |  | |||
|  | @ -0,0 +1,23 @@ | |||
| { | ||||
|   "name": "build-plugin-rax-kraken", | ||||
|   "version": "1.0.0-0", | ||||
|   "description": "rax kraken app plugin", | ||||
|   "main": "lib/index.js", | ||||
|   "scripts": { | ||||
|     "test": "echo \"Error: no test specified\" && exit 1" | ||||
|   }, | ||||
|   "keywords": [ | ||||
|     "plugin", | ||||
|     "rax" | ||||
|   ], | ||||
|   "author": "", | ||||
|   "license": "MIT", | ||||
|   "peerDependencies": { | ||||
|     "@alib/build-scripts": "^0.1.0", | ||||
|     "rax": "^1.0.0" | ||||
|   }, | ||||
|   "dependencies": { | ||||
|     "fs-extra": "^9.0.1", | ||||
|     "webpack-sources": "^2.0.0" | ||||
|   } | ||||
| } | ||||
|  | @ -0,0 +1,3 @@ | |||
| module.exports = { | ||||
|   GET_WEBPACK_BASE_CONFIG: 'getWebpackBaseConfig' | ||||
| }; | ||||
|  | @ -0,0 +1,39 @@ | |||
| const path = require('path'); | ||||
| const setEntry = require('./setEntry'); | ||||
| const { GET_WEBPACK_BASE_CONFIG } = require('./constants'); | ||||
| 
 | ||||
| module.exports = (api) => { | ||||
|   const { getValue, context, registerTask, onGetWebpackConfig } = api; | ||||
| 
 | ||||
|   const getWebpackBase = getValue(GET_WEBPACK_BASE_CONFIG); | ||||
|   const target = 'kraken'; | ||||
|   const chainConfig = getWebpackBase(api, { | ||||
|     target, | ||||
|     babelConfigOptions: { styleSheet: true }, | ||||
|     progressOptions: { | ||||
|       name: 'Kraken' | ||||
|     } | ||||
|   }); | ||||
| 
 | ||||
|   setEntry(chainConfig, context); | ||||
| 
 | ||||
|   registerTask(target, chainConfig); | ||||
| 
 | ||||
|   onGetWebpackConfig(target, config => { | ||||
|     const { userConfig, rootDir, command } = context; | ||||
|     const { outputDir = 'build' } = userConfig; | ||||
|     let outputPath; | ||||
|     if (command === 'start') { | ||||
|       // Set output dir
 | ||||
|       outputPath = path.resolve(rootDir, outputDir); | ||||
|       config.output.filename(`${target}/[name].js`); | ||||
|       // Force disable HMR, kraken not support yet.
 | ||||
|       config.devServer.inline(false); | ||||
|       config.devServer.hot(false); | ||||
|     } else if (command === 'build') { | ||||
|       // Set output dir
 | ||||
|       outputPath = path.resolve(rootDir, outputDir, target); | ||||
|     } | ||||
|     config.output.path(outputPath); | ||||
|   }); | ||||
| }; | ||||
|  | @ -0,0 +1,37 @@ | |||
| const fs = require('fs-extra'); | ||||
| const path = require('path'); | ||||
| 
 | ||||
| module.exports = (config, context) => { | ||||
|   const { rootDir } = context; | ||||
|   const target = 'kraken'; | ||||
| 
 | ||||
|   // SPA
 | ||||
|   const appEntry = moduleResolve(formatPath(path.join(rootDir, './src/app'))); | ||||
|   const entryConfig = config.entry('index'); | ||||
| 
 | ||||
|   config.module.rule('appJSON') | ||||
|     .use('loader') | ||||
|     .tap(() => ({ type: target })); | ||||
| 
 | ||||
|   ['jsx', 'tsx'].forEach(tag => { | ||||
|     config.module.rule(tag) | ||||
|       .use('platform-loader') | ||||
|       .options({ | ||||
|         platform: target, | ||||
|       }); | ||||
|   }); | ||||
| 
 | ||||
|   entryConfig.add(appEntry); | ||||
| }; | ||||
| 
 | ||||
| function moduleResolve(filePath) { | ||||
|   const ext = ['.ts', '.js', '.tsx', '.jsx'].find(extension => fs.existsSync(`${filePath}${extension}`)); | ||||
|   if (!ext) { | ||||
|     throw new Error(`Cannot find target file ${filePath}.`); | ||||
|   } | ||||
|   return require.resolve(`${filePath}${ext}`); | ||||
| } | ||||
| 
 | ||||
| function formatPath(pathStr) { | ||||
|   return process.platform === 'win32' ? pathStr.split(path.sep).join('/') : pathStr; | ||||
| } | ||||
|  | @ -0,0 +1,26 @@ | |||
| { | ||||
|   "name": "build-plugin-rax-miniapp", | ||||
|   "version": "1.0.0-0", | ||||
|   "description": "rax miniapp app plugin", | ||||
|   "main": "lib/index.js", | ||||
|   "scripts": { | ||||
|     "test": "echo \"Error: no test specified\" && exit 1" | ||||
|   }, | ||||
|   "keywords": [ | ||||
|     "plugin", | ||||
|     "rax" | ||||
|   ], | ||||
|   "author": "", | ||||
|   "license": "MIT", | ||||
|   "peerDependencies": { | ||||
|     "@alib/build-scripts": "^0.1.0", | ||||
|     "rax": "^1.0.0" | ||||
|   }, | ||||
|   "dependencies": { | ||||
|     "fs-extra": "^9.0.1", | ||||
|     "miniapp-runtime-config": "^0.1.2", | ||||
|     "rax-compile-config": "^0.2.16", | ||||
|     "webpack-sources": "^2.0.0", | ||||
|     "miniapp-builder-shared": "^0.1.5" | ||||
|   } | ||||
| } | ||||
|  | @ -0,0 +1,3 @@ | |||
| module.exports = { | ||||
|   GET_WEBPACK_BASE_CONFIG: 'getWebpackBaseConfig' | ||||
| }; | ||||
|  | @ -0,0 +1,44 @@ | |||
| const path = require('path'); | ||||
| const { platformMap } = require('miniapp-builder-shared'); | ||||
| const { setConfig } = require('miniapp-runtime-config'); | ||||
| const setEntry = require('./setEntry'); | ||||
| const { GET_WEBPACK_BASE_CONFIG } = require('./constants'); | ||||
| 
 | ||||
| module.exports = (api) => { | ||||
|   const { getValue, context, registerTask, onGetWebpackConfig } = api; | ||||
|   const { userConfig } = context; | ||||
|   const { targets } = userConfig; | ||||
| 
 | ||||
|   const getWebpackBase = getValue(GET_WEBPACK_BASE_CONFIG); | ||||
|   targets.forEach(target => { | ||||
|     if (['miniapp', 'wechat-miniprogram', 'bytedance-microapp'].includes(target)) { | ||||
|       const chainConfig = getWebpackBase(api, { | ||||
|         target, | ||||
|         babelConfigOptions: { styleSheet: true, disableRegenerator: true }, | ||||
|         progressOptions: { | ||||
|           name: platformMap[target].name | ||||
|         } | ||||
|       }); | ||||
|       // Set Entry
 | ||||
|       setEntry(chainConfig, context, target); | ||||
|       // Register task
 | ||||
|       registerTask(target, chainConfig); | ||||
| 
 | ||||
|       onGetWebpackConfig(target, config => { | ||||
|         const { userConfig, rootDir } = context; | ||||
|         const { outputDir = 'build' } = userConfig; | ||||
|         // Set output dir
 | ||||
|         const outputPath = path.resolve(rootDir, outputDir, target); | ||||
| 
 | ||||
|         config.output.path(outputPath); | ||||
| 
 | ||||
|         setConfig(config, userConfig[target] || {}, { | ||||
|           context, | ||||
|           target, | ||||
|           babelRuleName: 'babel-loader', | ||||
|           modernMode: true | ||||
|         }); | ||||
|       }); | ||||
|     } | ||||
|   }); | ||||
| }; | ||||
Some files were not shown because too many files have changed in this diff Show More
		Loading…
	
		Reference in New Issue