feat: the next rax app framework

This commit is contained in:
狒狒神 2020-09-28 20:54:38 +08:00 committed by GitHub
parent d2c5df448b
commit 907546003e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
133 changed files with 6067 additions and 2200 deletions

View File

@ -29,6 +29,7 @@ const tsRules = deepmerge(tslint, {
"@typescript-eslint/no-explicit-any": 0,
"@typescript-eslint/interface-name-prefix": 0,
"@typescript-eslint/explicit-function-return-type": 0,
"@typescript-eslint/no-var-requires": 0
},
});

View File

@ -1,4 +1,9 @@
{
"web": {
"mpa": true,
"ssr": true
},
"plugins": [],
"targets": ["web"]
"inlineStyle": true,
"targets": ["web", "weex"]
}

View File

@ -1,15 +1,21 @@
import { runApp } from 'rax-app';
runApp({
router: {
type: 'browser'
},
app: {
onShow() {
console.log('app show...');
},
onHide() {
console.log('app hide...');
},
getInitialData: async () => {
return {
a: 1,
b: 2
};
}
},
router: {
basename: '/home'
}
});

View File

@ -1,6 +1,5 @@
import { createElement, PureComponent } from 'rax';
import Image from 'rax-image';
import { withRouter } from 'rax-app';
import './index.css';
@ -19,4 +18,4 @@ class Logo extends PureComponent {
}
}
export default withRouter(Logo);
export default Logo;

View File

@ -1,7 +1,8 @@
import { createElement } from 'rax';
import { Root, Style, Script } from 'rax-document';
import { Root, Style, Script, Data } from 'rax-document';
function Document() {
function Document(props) {
console.log('document props:', props);
return (
<html>
<head>
@ -13,6 +14,7 @@ function Document() {
<body>
{/* root container */}
<Root />
<Data />
<Script />
</body>
</html>

View File

@ -0,0 +1,11 @@
{
"routes": [
{
"path": "/",
"source": "/pages/About/index"
}
],
"window": {
"title": "Rax App"
}
}

View File

@ -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);

View File

@ -5,6 +5,7 @@ import { getSearchParams, withPageLifeCycle } from 'rax-app';
import './index.css';
@withPageLifeCycle
class About extends PureComponent {
public componentDidMount() {
console.log('about search params', getSearchParams());
@ -21,11 +22,11 @@ class About extends PureComponent {
public render() {
return (
<View className="about">
<Text className="title">About Page!!!</Text>
<Text className="info" onClick={() => this.props.history.push('/')}>Go Home</Text>
<Text className="title">About Page</Text>
<Text className="info" onClick={() => (this.props as any).history.push('/')}>Go Home</Text>
</View>
);
}
}
export default withPageLifeCycle(About);
export default About;

View File

@ -4,7 +4,7 @@
}
.title {
font-size: 45rpx;
font-size: 35rpx;
font-weight: bold;
margin: 20rpx 0;
}

View File

@ -12,6 +12,7 @@ export default function Home(props) {
const searchParams = getSearchParams();
console.log('home search params =>', searchParams);
console.log('home page props =>', props);
usePageShow(() => {
console.log('home show...');
@ -24,9 +25,18 @@ export default function Home(props) {
return (
<View className="home">
<Logo />
<Text className="title">Welcome to Your Rax App!!!</Text>
<Text className="info">More information about Rax</Text>
<Text className="title">{props?.data?.title || 'Welcome to Your Rax App'}</Text>
<Text className="info">{props?.data?.info || 'More information about Rax'}</Text>
<Text className="info" onClick={() => history.push('/about?id=1')}>Go About</Text>
</View>
);
}
Home.getInitialProps = async () => {
return {
data: {
title: 'Welcome to Your Rax App with SSR',
info: 'More information about Rax'
}
};
};

View File

@ -64,6 +64,7 @@
"typescript": "^3.7.4"
},
"dependencies": {
"core-js": "^3.6.4"
"core-js": "^3.6.4",
"path-to-regexp": "6.1.0"
}
}

View File

@ -0,0 +1 @@
# build-mpa-config

View File

@ -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"
}
}

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -0,0 +1,8 @@
{
"extends": "../../tsconfig.settings.json",
"compilerOptions": {
"baseUrl": "./",
"rootDir": "src",
"outDir": "lib"
}
}

View File

@ -35,7 +35,7 @@ const configCSSRule = (config, style, mode, loaders = []) => {
.exclude.add(cssModuleReg).end();
}
if (mode === 'development') {
if (mode === 'development') {
const cssHotLoader = rule.use('css-hot-loader')
.loader(require.resolve('css-hot-loader'));
if (ruleKey === 'module') {

View File

@ -3,5 +3,8 @@ import { getHistory } from './history';
export default function() {
const history = getHistory();
return queryString.parse(history.location.search);
if (history && history.location) {
return queryString.parse(history.location.search);
}
return {};
}

View File

@ -4,7 +4,7 @@ import {
createMemoryHistory
} from 'history';
import { createMiniAppHistory } from 'miniapp-history';
import { isMiniAppPlatform } from './env';
import { isMiniAppPlatform, isWeex, isKraken } from './env';
// eslint-disable-next-line
let history;
@ -12,16 +12,22 @@ let history;
function createHistory({ routes, customHistory, type, basename }: any) {
if (customHistory) {
history = customHistory;
} else if (type === 'hash') {
history = createHashHistory({ basename });
} else if (type === 'browser') {
history = createBrowserHistory({ basename });
} else if (isMiniAppPlatform) {
(window as any).history = createMiniAppHistory(routes);
window.location = (window.history as any).location;
history = window.history;
} else {
history = createMemoryHistory();
// Force memory history when env is weex or kraken
if (isWeex || isKraken) {
type = 'memory';
}
if (type === 'hash') {
history = createHashHistory({ basename });
} else if (type === 'browser') {
history = createBrowserHistory({ basename });
} else if (isMiniAppPlatform) {
(window as any).history = createMiniAppHistory(routes);
window.location = (window.history as any).location;
history = window.history;
} else {
history = createMemoryHistory();
}
}
return history;
}

View File

@ -12,27 +12,46 @@ const configPath = path.resolve(rawArgv.config || 'build.json');
const inspectRegExp = /^--(inspect(?:-brk)?)(?:=(?:([^:]+):)?(\d+))?$/;
function modifyInspectArgv(argv) {
return Promise.all(
argv.map(async item => {
async function modifyInspectArgv(execArgv, processArgv) {
/**
* Enable debugger by exec argv, eg. node --inspect node_modules/.bin/build-scripts start
* By this way, there will be two inspector, because start.js is run as a child process.
* So need to handle the conflict of port.
*/
const result = await Promise.all(
execArgv.map(async item => {
const matchResult = inspectRegExp.exec(item);
if (!matchResult) {
return item;
}
// eslint-disable-next-line no-unused-vars
const [_, command, ip, port = 9229] = matchResult;
const nPort = +port;
const newPort = await detect(nPort);
return `--${command}=${ip ? `${ip}:` : ''}${newPort}`;
})
);
/**
* Enable debugger by process argv, eg. npm run start --inspect
* Need to change it as an exec argv.
*/
if (processArgv.inspect) {
const matchResult = /(?:([^:]+):)?(\d+)/.exec(rawArgv.inspect);
const [_, ip, port = 9229] = matchResult || [];
const newPort = await detect(port);
result.push(`--inspect-brk=${ip ? `${ip}:` : ''}${newPort}`);
}
return result;
}
function restartProcess(forkChildProcessPath) {
(async () => {
// remove the inspect related argv when passing to child process to avoid port-in-use error
const argv = await modifyInspectArgv(process.execArgv);
child = fork(forkChildProcessPath, process.argv.slice(2), { execArgv: argv });
const argv = await modifyInspectArgv(process.execArgv, rawArgv);
const nProcessArgv = process.argv.slice(2).filter((arg) => arg.indexOf('--inspect') === -1);
child = fork(forkChildProcessPath, nProcessArgv, { execArgv: argv });
child.on('message', data => {
if (process.send) {
process.send(data);

View File

@ -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"
}
}

View File

@ -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;
};

View File

@ -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' }]);
}
};

View File

@ -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];
});
}
};

View File

@ -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);
}
};

View File

@ -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);
}
};

View File

@ -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
};

View File

@ -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'],
},
};

View File

@ -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'
};

View File

@ -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'
};

View File

@ -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);
}
};

View File

@ -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}`;
}
};

View File

@ -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;
`;
};

View File

@ -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;
});
};

View File

@ -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 [];
}
}

View File

@ -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;
}));
};

View File

@ -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));
};

View File

@ -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();
}
}
});
};

View File

@ -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;
}

View File

@ -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,
};
});
};

View File

@ -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,
},
});
};

View File

@ -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,
],
};
});
});
};

View File

@ -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,
],
};
});
});
};

View File

@ -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 });
});
});
};

View File

@ -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);
});
};

View File

@ -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,
}));
}
});
}
};

View File

@ -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)]);
}
};

View File

@ -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);
}
};

View File

@ -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 } });
};

View File

@ -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');
}

View File

@ -0,0 +1 @@
module.exports = () => {};

View File

@ -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
}));
}
};

View File

@ -0,0 +1,8 @@
module.exports = (config, extensions) => {
if (Array.isArray(extensions)) {
extensions.forEach((extension) => {
config.resolve.extensions
.add(extension);
});
}
};

View File

@ -0,0 +1,3 @@
module.exports = (config, value) => {
config.merge({ externals: value });
};

View File

@ -0,0 +1,5 @@
module.exports = (config, filename) => {
if (filename) {
config.output.filename(filename);
}
};

View File

@ -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`)),
})]);
}
}
};

View File

@ -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 }]]);
}
};

View File

@ -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
},
}
};
}

View File

@ -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,
}));
}
});
}
};

View File

@ -0,0 +1,5 @@
module.exports = (config, library) => {
if (library) {
config.output.library(library);
}
};

View File

@ -0,0 +1,5 @@
module.exports = (config, libraryExport) => {
if (libraryExport) {
config.output.libraryExport(libraryExport);
}
};

View File

@ -0,0 +1,5 @@
module.exports = (config, libraryTarget) => {
if (libraryTarget) {
config.output.libraryTarget(libraryTarget);
}
};

View File

@ -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);
};

View File

@ -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);
}
},
}});
}
};

View File

@ -0,0 +1,8 @@
module.exports = (config, modules) => {
if (Array.isArray(modules)) {
modules.forEach((module) => {
config.resolve.modules
.add(module);
});
}
};

View File

@ -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))),
})]);
}
};

View File

@ -0,0 +1,18 @@
const path = require('path');
module.exports = (config, outputDir, context) => {
const { rootDir } = context;
// outputPathbuild/*
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,
}]]);
}
};

View File

@ -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(() => ({}));
}
});
}
};

View File

@ -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);
}
};

View File

@ -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);
}
};

View File

@ -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,
}));
}
});
}
};

View File

@ -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 },
]);
}
};

View File

@ -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,
},
]);
}
};

View File

@ -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);
}
};

View File

@ -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,
},
} });
}
};

View File

@ -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}/`);
}
}
},
},
};
};

View File

@ -0,0 +1,5 @@
module.exports = (outputPath) => {
const isWin = process.platform === 'win32';
// js\index.js => js/index.js
return isWin ? outputPath.replace(/\\/g, '/') : outputPath;
};

View File

@ -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);
});
};

View File

@ -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);
}
}
};

View File

@ -0,0 +1,7 @@
module.exports = (content) => {
return `
import "core-js/stable";
import "regenerator-runtime/runtime";
${content}
`;
};

View File

@ -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 }));
}
});
}
};

View File

@ -1,6 +1,6 @@
{
"name": "build-plugin-app-core",
"version": "0.1.18",
"version": "0.1.19",
"description": "the core plugin for icejs and raxjs.",
"author": "ice-admin@alibaba-inc.com",
"homepage": "",

View File

@ -5,7 +5,7 @@ interface IProps {
error: Error;
}
const toTitle = (error: Error, componentStack: string): string => {
const toTitle = (error: Error): string => {
return `${error.toString()}`;
};

View File

@ -46,11 +46,7 @@ class ErrorBoundary extends Component<IProps, IState> {
const { error } = this.state;
// render fallback UI if there is error
if (error !== null && typeof Fallback === 'function') {
return (
<Fallback
error={error}
/>
);
}
return children || null;

View File

@ -17,7 +17,7 @@ import createShareAPI, { history } from 'create-app-shared';
import loadRuntimeModules from './loadRuntimeModules';
import loadStaticModules from './loadStaticModules';
import staticConfig from './staticConfig';
import defaultStaticConfig from './staticConfig';
import { setAppConfig } from './appConfig';
import { mount, unmount } from './render';
import ErrorBoundary from './ErrorBoundary';
@ -46,7 +46,7 @@ const {
withRouter: defaultWithRouter
}, loadRuntimeModules);
export function runApp(appConfig) {
export function runApp(appConfig, staticConfig?: any) {
let renderer;
<% if(isRax){ %>
renderer = raxAppRenderer;
@ -59,7 +59,7 @@ export function runApp(appConfig) {
<% } %>
renderer({
appConfig,
staticConfig,
staticConfig: staticConfig || defaultStaticConfig,
setAppConfig,
createBaseApp,
createHistory,

View File

@ -116,6 +116,6 @@ function checkTargets(targets) {
function matchTargets(targets) {
return targets.every(target => {
return ['web', 'miniapp', 'wechat-miniprogram'].includes(target);
return ['web', 'miniapp', 'wechat-miniprogram', 'weex', 'kraken', 'bytedance-microapp', 'quickapp'].includes(target);
});
}

View File

@ -0,0 +1 @@
# plugin-ssr

View File

@ -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"
}
}

View File

@ -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;

View File

@ -0,0 +1,8 @@
{
"extends": "../../tsconfig.settings.json",
"compilerOptions": {
"baseUrl": "./",
"rootDir": "src",
"outDir": "lib"
},
}

View File

@ -11,14 +11,16 @@ module.exports = (api) => {
if (target === 'miniapp' || target === 'wechat-miniprogram') {
onGetWebpackConfig(target, (config) => {
const projectType = getValue('PROJECT_TYPE');
const { outputDir = 'build' } = userConfig;
// Clear entry
config.entryPoints.clear();
// App entry
config.entry('index').add(builderShared.pathHelper.getDepPath(rootDir, `app.${projectType}`));
config.output.path(path.join(rootDir, 'build'));
const outputPath = path.resolve(rootDir, outputDir, target);
config.output.path(path.join(rootDir, 'build', target));
miniappConfig.setConfig(config, userConfig[target] || {}, { context, target, babelRuleName: 'babel-loader', onGetWebpackConfig });
miniappConfig.setConfig(config, userConfig[target] || {}, { context, target, babelRuleName: 'babel-loader', outputPath });
if (config.plugins.get('MiniCssExtractPlugin')) {
config.plugin('MiniCssExtractPlugin').tap((args) => [

View File

@ -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"
}
}

View File

@ -0,0 +1,3 @@
module.exports = {
GET_WEBPACK_BASE_CONFIG: 'getWebpackBaseConfig'
};

View File

@ -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);
});
};

View File

@ -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;
}

View File

@ -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"
}
}

View File

@ -0,0 +1,3 @@
module.exports = {
GET_WEBPACK_BASE_CONFIG: 'getWebpackBaseConfig'
};

View File

@ -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