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/no-explicit-any": 0,
|
||||||
"@typescript-eslint/interface-name-prefix": 0,
|
"@typescript-eslint/interface-name-prefix": 0,
|
||||||
"@typescript-eslint/explicit-function-return-type": 0,
|
"@typescript-eslint/explicit-function-return-type": 0,
|
||||||
|
"@typescript-eslint/no-var-requires": 0
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,9 @@
|
||||||
{
|
{
|
||||||
|
"web": {
|
||||||
|
"mpa": true,
|
||||||
|
"ssr": true
|
||||||
|
},
|
||||||
"plugins": [],
|
"plugins": [],
|
||||||
"targets": ["web"]
|
"inlineStyle": true,
|
||||||
|
"targets": ["web", "weex"]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,21 @@
|
||||||
import { runApp } from 'rax-app';
|
import { runApp } from 'rax-app';
|
||||||
|
|
||||||
runApp({
|
runApp({
|
||||||
|
router: {
|
||||||
|
type: 'browser'
|
||||||
|
},
|
||||||
app: {
|
app: {
|
||||||
onShow() {
|
onShow() {
|
||||||
console.log('app show...');
|
console.log('app show...');
|
||||||
},
|
},
|
||||||
onHide() {
|
onHide() {
|
||||||
console.log('app hide...');
|
console.log('app hide...');
|
||||||
|
},
|
||||||
|
getInitialData: async () => {
|
||||||
|
return {
|
||||||
|
a: 1,
|
||||||
|
b: 2
|
||||||
|
};
|
||||||
}
|
}
|
||||||
},
|
|
||||||
router: {
|
|
||||||
basename: '/home'
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
import { createElement, PureComponent } from 'rax';
|
import { createElement, PureComponent } from 'rax';
|
||||||
import Image from 'rax-image';
|
import Image from 'rax-image';
|
||||||
import { withRouter } from 'rax-app';
|
|
||||||
|
|
||||||
import './index.css';
|
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 { 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 (
|
return (
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
|
|
@ -13,6 +14,7 @@ function Document() {
|
||||||
<body>
|
<body>
|
||||||
{/* root container */}
|
{/* root container */}
|
||||||
<Root />
|
<Root />
|
||||||
|
<Data />
|
||||||
<Script />
|
<Script />
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</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';
|
import './index.css';
|
||||||
|
|
||||||
|
@withPageLifeCycle
|
||||||
class About extends PureComponent {
|
class About extends PureComponent {
|
||||||
public componentDidMount() {
|
public componentDidMount() {
|
||||||
console.log('about search params', getSearchParams());
|
console.log('about search params', getSearchParams());
|
||||||
|
|
@ -21,11 +22,11 @@ class About extends PureComponent {
|
||||||
public render() {
|
public render() {
|
||||||
return (
|
return (
|
||||||
<View className="about">
|
<View className="about">
|
||||||
<Text className="title">About Page!!!</Text>
|
<Text className="title">About Page</Text>
|
||||||
<Text className="info" onClick={() => this.props.history.push('/')}>Go Home</Text>
|
<Text className="info" onClick={() => (this.props as any).history.push('/')}>Go Home</Text>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default withPageLifeCycle(About);
|
export default About;
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.title {
|
.title {
|
||||||
font-size: 45rpx;
|
font-size: 35rpx;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
margin: 20rpx 0;
|
margin: 20rpx 0;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ export default function Home(props) {
|
||||||
const searchParams = getSearchParams();
|
const searchParams = getSearchParams();
|
||||||
|
|
||||||
console.log('home search params =>', searchParams);
|
console.log('home search params =>', searchParams);
|
||||||
|
console.log('home page props =>', props);
|
||||||
|
|
||||||
usePageShow(() => {
|
usePageShow(() => {
|
||||||
console.log('home show...');
|
console.log('home show...');
|
||||||
|
|
@ -24,9 +25,18 @@ export default function Home(props) {
|
||||||
return (
|
return (
|
||||||
<View className="home">
|
<View className="home">
|
||||||
<Logo />
|
<Logo />
|
||||||
<Text className="title">Welcome to Your Rax App!!!</Text>
|
<Text className="title">{props?.data?.title || 'Welcome to Your Rax App'}</Text>
|
||||||
<Text className="info">More information about Rax</Text>
|
<Text className="info">{props?.data?.info || 'More information about Rax'}</Text>
|
||||||
<Text className="info" onClick={() => history.push('/about?id=1')}>Go About</Text>
|
<Text className="info" onClick={() => history.push('/about?id=1')}>Go About</Text>
|
||||||
</View>
|
</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"
|
"typescript": "^3.7.4"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"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();
|
.exclude.add(cssModuleReg).end();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mode === 'development') {
|
if (mode === 'development') {
|
||||||
const cssHotLoader = rule.use('css-hot-loader')
|
const cssHotLoader = rule.use('css-hot-loader')
|
||||||
.loader(require.resolve('css-hot-loader'));
|
.loader(require.resolve('css-hot-loader'));
|
||||||
if (ruleKey === 'module') {
|
if (ruleKey === 'module') {
|
||||||
|
|
|
||||||
|
|
@ -3,5 +3,8 @@ import { getHistory } from './history';
|
||||||
|
|
||||||
export default function() {
|
export default function() {
|
||||||
const history = getHistory();
|
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
|
createMemoryHistory
|
||||||
} from 'history';
|
} from 'history';
|
||||||
import { createMiniAppHistory } from 'miniapp-history';
|
import { createMiniAppHistory } from 'miniapp-history';
|
||||||
import { isMiniAppPlatform } from './env';
|
import { isMiniAppPlatform, isWeex, isKraken } from './env';
|
||||||
|
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
let history;
|
let history;
|
||||||
|
|
@ -12,16 +12,22 @@ let history;
|
||||||
function createHistory({ routes, customHistory, type, basename }: any) {
|
function createHistory({ routes, customHistory, type, basename }: any) {
|
||||||
if (customHistory) {
|
if (customHistory) {
|
||||||
history = 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 {
|
} 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;
|
return history;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,27 +12,46 @@ const configPath = path.resolve(rawArgv.config || 'build.json');
|
||||||
|
|
||||||
const inspectRegExp = /^--(inspect(?:-brk)?)(?:=(?:([^:]+):)?(\d+))?$/;
|
const inspectRegExp = /^--(inspect(?:-brk)?)(?:=(?:([^:]+):)?(\d+))?$/;
|
||||||
|
|
||||||
function modifyInspectArgv(argv) {
|
async function modifyInspectArgv(execArgv, processArgv) {
|
||||||
return Promise.all(
|
/**
|
||||||
argv.map(async item => {
|
* 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);
|
const matchResult = inspectRegExp.exec(item);
|
||||||
if (!matchResult) {
|
if (!matchResult) {
|
||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line no-unused-vars
|
|
||||||
const [_, command, ip, port = 9229] = matchResult;
|
const [_, command, ip, port = 9229] = matchResult;
|
||||||
const nPort = +port;
|
const nPort = +port;
|
||||||
const newPort = await detect(nPort);
|
const newPort = await detect(nPort);
|
||||||
return `--${command}=${ip ? `${ip}:` : ''}${newPort}`;
|
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) {
|
function restartProcess(forkChildProcessPath) {
|
||||||
(async () => {
|
(async () => {
|
||||||
// remove the inspect related argv when passing to child process to avoid port-in-use error
|
// remove the inspect related argv when passing to child process to avoid port-in-use error
|
||||||
const argv = await modifyInspectArgv(process.execArgv);
|
const argv = await modifyInspectArgv(process.execArgv, rawArgv);
|
||||||
child = fork(forkChildProcessPath, process.argv.slice(2), { execArgv: argv });
|
const nProcessArgv = process.argv.slice(2).filter((arg) => arg.indexOf('--inspect') === -1);
|
||||||
|
child = fork(forkChildProcessPath, nProcessArgv, { execArgv: argv });
|
||||||
child.on('message', data => {
|
child.on('message', data => {
|
||||||
if (process.send) {
|
if (process.send) {
|
||||||
process.send(data);
|
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",
|
"name": "build-plugin-app-core",
|
||||||
"version": "0.1.18",
|
"version": "0.1.19",
|
||||||
"description": "the core plugin for icejs and raxjs.",
|
"description": "the core plugin for icejs and raxjs.",
|
||||||
"author": "ice-admin@alibaba-inc.com",
|
"author": "ice-admin@alibaba-inc.com",
|
||||||
"homepage": "",
|
"homepage": "",
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ interface IProps {
|
||||||
error: Error;
|
error: Error;
|
||||||
}
|
}
|
||||||
|
|
||||||
const toTitle = (error: Error, componentStack: string): string => {
|
const toTitle = (error: Error): string => {
|
||||||
return `${error.toString()}`;
|
return `${error.toString()}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -46,11 +46,7 @@ class ErrorBoundary extends Component<IProps, IState> {
|
||||||
const { error } = this.state;
|
const { error } = this.state;
|
||||||
// render fallback UI if there is error
|
// render fallback UI if there is error
|
||||||
if (error !== null && typeof Fallback === 'function') {
|
if (error !== null && typeof Fallback === 'function') {
|
||||||
return (
|
|
||||||
<Fallback
|
|
||||||
error={error}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return children || null;
|
return children || null;
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ import createShareAPI, { history } from 'create-app-shared';
|
||||||
|
|
||||||
import loadRuntimeModules from './loadRuntimeModules';
|
import loadRuntimeModules from './loadRuntimeModules';
|
||||||
import loadStaticModules from './loadStaticModules';
|
import loadStaticModules from './loadStaticModules';
|
||||||
import staticConfig from './staticConfig';
|
import defaultStaticConfig from './staticConfig';
|
||||||
import { setAppConfig } from './appConfig';
|
import { setAppConfig } from './appConfig';
|
||||||
import { mount, unmount } from './render';
|
import { mount, unmount } from './render';
|
||||||
import ErrorBoundary from './ErrorBoundary';
|
import ErrorBoundary from './ErrorBoundary';
|
||||||
|
|
@ -46,7 +46,7 @@ const {
|
||||||
withRouter: defaultWithRouter
|
withRouter: defaultWithRouter
|
||||||
}, loadRuntimeModules);
|
}, loadRuntimeModules);
|
||||||
|
|
||||||
export function runApp(appConfig) {
|
export function runApp(appConfig, staticConfig?: any) {
|
||||||
let renderer;
|
let renderer;
|
||||||
<% if(isRax){ %>
|
<% if(isRax){ %>
|
||||||
renderer = raxAppRenderer;
|
renderer = raxAppRenderer;
|
||||||
|
|
@ -59,7 +59,7 @@ export function runApp(appConfig) {
|
||||||
<% } %>
|
<% } %>
|
||||||
renderer({
|
renderer({
|
||||||
appConfig,
|
appConfig,
|
||||||
staticConfig,
|
staticConfig: staticConfig || defaultStaticConfig,
|
||||||
setAppConfig,
|
setAppConfig,
|
||||||
createBaseApp,
|
createBaseApp,
|
||||||
createHistory,
|
createHistory,
|
||||||
|
|
|
||||||
|
|
@ -116,6 +116,6 @@ function checkTargets(targets) {
|
||||||
|
|
||||||
function matchTargets(targets) {
|
function matchTargets(targets) {
|
||||||
return targets.every(target => {
|
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') {
|
if (target === 'miniapp' || target === 'wechat-miniprogram') {
|
||||||
onGetWebpackConfig(target, (config) => {
|
onGetWebpackConfig(target, (config) => {
|
||||||
const projectType = getValue('PROJECT_TYPE');
|
const projectType = getValue('PROJECT_TYPE');
|
||||||
|
const { outputDir = 'build' } = userConfig;
|
||||||
// Clear entry
|
// Clear entry
|
||||||
config.entryPoints.clear();
|
config.entryPoints.clear();
|
||||||
// App entry
|
// App entry
|
||||||
config.entry('index').add(builderShared.pathHelper.getDepPath(rootDir, `app.${projectType}`));
|
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')) {
|
if (config.plugins.get('MiniCssExtractPlugin')) {
|
||||||
config.plugin('MiniCssExtractPlugin').tap((args) => [
|
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