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