@plugin-light/project-config-pixui
Version:
开箱即用的项目配置,适用于 pixui 项目
346 lines (336 loc) • 13.9 kB
JavaScript
;
Object.defineProperty(exports, '__esModule', { value: true });
var path = require('path');
var fs = require('fs');
var cleanWebpackPlugin = require('clean-webpack-plugin');
var ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
var HtmlWebpackPlugin = require('html-webpack-plugin');
var webpack = require('webpack');
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
var path__default = /*#__PURE__*/_interopDefaultLegacy(path);
var fs__default = /*#__PURE__*/_interopDefaultLegacy(fs);
var ForkTsCheckerWebpackPlugin__default = /*#__PURE__*/_interopDefaultLegacy(ForkTsCheckerWebpackPlugin);
var HtmlWebpackPlugin__default = /*#__PURE__*/_interopDefaultLegacy(HtmlWebpackPlugin);
var webpack__default = /*#__PURE__*/_interopDefaultLegacy(webpack);
const DEFAULT_TSX_LOADER_EXCLUDE = /node_modules\/(?!@tencent\/pmd|@tencent\/press)/;
const DEFAULT_PORT = 8080;
/* eslint-disable @typescript-eslint/no-require-imports */
const pr$1 = path__default["default"].resolve;
function readEnv() {
const envPath = '.env.local';
if (!fs__default["default"].existsSync(envPath)) {
return;
}
try {
// eslint-disable-next-line @typescript-eslint/no-require-imports
require('dotenv').config({ path: envPath });
}
catch (e) { }
}
function getHtmlName(v) {
switch (v.name) {
case 'app': {
return 'index.html'; // app不允许自定义
}
case 'popup': {
return 'index.html'; // app不允许自定义
}
case 'preprocess': {
return 'preprocess.html'; // preprocess不允许自定义
}
default: {
return v.output ? `${v.output}.html` : `${v.name}.html`;
}
}
}
function getAppDefAndSettings(appSettingDir) {
let appDefs = require(pr$1(appSettingDir, 'apps.json'));
if (fs__default["default"].existsSync(pr$1(appSettingDir, 'custom.json'))) {
appDefs = [...appDefs, ...require(pr$1(appSettingDir, 'custom.json'))];
}
const appSettings = require(pr$1(appSettingDir, 'appsettings.json'));
return {
appDefs,
appSettings,
};
}
function getPlugins({ appDefs, appSettings, rootDir, isShipping, }) {
const htmlCopyPlugins = appDefs.map((app) => (app.template ? new HtmlWebpackPlugin__default["default"]({
hash: isShipping,
minify: isShipping,
chunks: [app.name],
template: app.template,
filename: `${app.name}/${getHtmlName(app)}`,
}) : false));
const plugins = [
new webpack__default["default"].DefinePlugin({
APP_SETTING: JSON.stringify(appSettings),
'process.env': JSON.stringify({
MOCK_LOGIN_PARAMS: process.env.MOCK_LOGIN_PARAMS || '',
}),
}),
new cleanWebpackPlugin.CleanWebpackPlugin({
cleanOnceBeforeBuildPatterns: [
'**/*',
path__default["default"].join(rootDir, '.build'),
],
}),
new ForkTsCheckerWebpackPlugin__default["default"]({ async: false }),
...htmlCopyPlugins,
!isShipping ? new webpack__default["default"].HotModuleReplacementPlugin() : undefined,
].filter(v => !!v);
return plugins;
}
function getRewrites(appDefs) {
const rewrites = appDefs.map((v) => ({ from: new RegExp(`^/${v.name}/`), to: `/${v.name}` }));
return rewrites;
}
const { rtCompilerToolPath } = process.env;
function getLocalIP() {
const os = require('os');
const ifaces = os.networkInterfaces();
const ipList = [];
Object.keys(ifaces).forEach((ifname) => {
// const alias = 0;
ifaces[ifname].forEach((iface) => {
if ('IPv4' !== iface.family || iface.internal !== false) {
// skip over internal (i.e. 127.0.0.1) and non-ipv4 addresses
return;
}
ipList.push(iface.address);
});
});
return ipList;
}
const setupFilter = (appDef, _port) => function (app) {
let log = (..._args) => { };
log = console.log;
app.disable('etag');
app.get('/log0', (req, res) => {
log = () => { };
res.send('ok');
});
app.get('/log1', (req, res) => {
log = console.log;
res.send('ok');
});
// 拦截输出,当agent为PixUI且输出类型为html时,转换为fbs二进制数据再返回
app.use((req, res, next) => {
// 去掉缓存标记,因为很可能源文件没变,但转换后的表示变了
delete req.headers['if-none-match'];
const ua = req.get('User-Agent');
log('req', req.url, ua, JSON.stringify(req.headers));
const _write = res.write;
const _end = res.end;
const _send = res.send;
const buffers = [];
const addBuffer = (chunk, encoding) => {
if (chunk === undefined)
return;
if (typeof chunk === 'string') {
chunk = Buffer.from(chunk, encoding);
}
buffers.push(chunk);
};
// 处理直接裸写的情况,一般见于发送静态文件
res.write = function (chunk, encoding) {
const type = res.get('Content-Type');
log('hook write...', type, chunk);
addBuffer(chunk, encoding);
};
res.end = function (chunk, encoding) {
const type = res.get('Content-Type');
if (chunk)
addBuffer(chunk, encoding);
const data = Buffer.concat(buffers);
log('hook end...', type, data.length);
res.write = _write;
res.end = _end;
_send.call(this, data);
};
// 处理通过send发送的情况,如webpack合成的html
res.send = function (c) {
const type = res.get('Content-Type') || 'text/plain';
const isPixUI = /PixUI/.test(ua);
const ver = /(\d)\.(\d)\.(\d+)/.exec(ua); // 0.2.*以上
const isHTML = type.indexOf('text/html') >= 0;
const isJS = type.indexOf('javascript') >= 0;
// const isCSS = type.indexOf('text/css') >= 0;
log('hook send...', c.length, type);
if (isPixUI && parseInt(`${ver?.[2]}`, 10) >= 2 && (isHTML || isJS)) {
const cp = require('child_process');
const opts = isJS ? ['--js'] : [];
const child = cp.spawn(rtCompilerToolPath, ['--src', 'stdin', ...opts]);
child.stdin.write(c);
child.stdout.on('data', (data) => {
// console.log('get data from fbs', req.url, data.byteLength);
addBuffer(data);
});
child.stdout.on('close', () => {
// console.log('get data from fbs finish!!', req.url);
res.end();
});
child.stderr.on('data', (data) => {
console.log('get err from fbs', data.toString());
});
child.stdin.end();
}
else {
_send.call(this, c);
}
};
next();
});
// 定制首页显示所有entry,方便浏览
app.get('/', (req, res) => {
res.setHeader('content-type', 'text/html');
res.send(`${appDef
.concat([{ name: 'test' }])
.map(v => `<a href="./${v.name}" style='font-size:36px'>${v.name}</a>`)
.join('<br>')}<div style='font-size:40pt' onclick='window.close();'>close</div>`);
});
};
/* eslint-disable @typescript-eslint/no-require-imports */
readEnv();
const pr = path__default["default"].resolve;
const isShipping = process.env.mode != 'development';
console.log({ isShipping });
function getPixuiWebpackConfig(options) {
const { appSettingDir, } = options || {};
const { appDefs, appSettings, } = getAppDefAndSettings(appSettingDir);
const innerTsxLoaderExclude = options?.tsxLoaderExclude ?? DEFAULT_TSX_LOADER_EXCLUDE;
const innerPort = options?.port || process.env.port || DEFAULT_PORT;
const useTailwind = options?.useTailwind ?? false;
const rootDir = pr(appSettingDir, '..', 'dist');
const pandoraHtmlDir = pr(appSettingDir, '..');
const buildOutputPath = pr(rootDir, `html${isShipping ? '-pro' : '-dev'}`);
const result = {
entry: Object.fromEntries(appDefs.map(v => [v.name, v.entry])),
output: {
path: buildOutputPath,
filename: '[name]/[name].js',
publicPath: isShipping
? '../' // 发布后都从文件系统加载,使用相对路径引用素材
: '/', // `http://${getLocalIP()[0]}:${innerPort}/`, //开发时绑定到外部ip,方便手机访问
},
resolve: {
modules: [`${pandoraHtmlDir}/src/node_modules`, pandoraHtmlDir, `${pandoraHtmlDir}/node_modules`],
extensions: ['.js', '.jsx', '.ts', '.tsx'],
alias: {
['preact$']: path__default["default"].resolve('./lib/preact/src'),
['preact/hooks$']: path__default["default"].resolve('./lib/preact/hooks/src'),
// lib 中的 preact-router 不支持 history属性,改用 node_modules 中的
// ['preact-router$']: path.resolve('./lib/preact-router/src'),
['preact-router/match']: path__default["default"].resolve('./lib/preact-router/match/src'),
['react-window']: path__default["default"].resolve('./lib/react-window/src'),
['@improbable-eng/grpc-web']: path__default["default"].resolve('./lib/grpc-web/dist'),
react: path__default["default"].resolve('./lib/preact/compat/src'),
},
},
module: {
rules: [
{
test: /.(js|ts)x?$/,
use: {
loader: require.resolve('babel-loader'),
options: {
presets: [['@babel/preset-typescript', { allowNamespaces: true }]],
plugins: [
'@babel/plugin-transform-flow-strip-types',
'@babel/plugin-syntax-jsx',
['@babel/plugin-proposal-decorators', { legacy: true }],
['@babel/plugin-proposal-class-properties'],
['@babel/plugin-proposal-optional-chaining'],
['@babel/plugin-transform-react-jsx', { pragma: 'makeDOM' }],
['babel-plugin-jsx-pragmatic', { module: `${appSettingDir}/../lib/dom`, import: 'makeDOM' }],
],
},
},
exclude: innerTsxLoaderExclude,
},
{
test: /\.css$/,
use: [
require.resolve('style-loader'),
{
loader: require.resolve('css-loader'),
options: useTailwind ? {} : { modules: true },
},
useTailwind ? require.resolve('postcss-loader') : '',
].filter(Boolean),
},
{
test: /\.less$/,
use: [
'style-loader',
{
loader: require.resolve('css-loader'),
options: {
modules: {
localIdentName: '[name]_[local]__[hash:base64:6]',
},
},
},
{
loader: require.resolve('less-loader'),
options: { javascriptEnabled: true },
},
],
},
{
test: /\.s[ac]ss$/i,
use: [
'style-loader',
{
loader: require.resolve('css-loader'),
options: { modules: true },
},
'sass-loader',
],
},
{
test: /\.(png|jpg|gif|lottie|mp4)$/,
loader: require.resolve('file-loader'),
options: {
name: 'static/media/[name].[hash:8].[ext]',
},
},
],
},
optimization: {
minimize: false,
},
devtool: 'source-map',
plugins: getPlugins({
appDefs,
appSettings,
rootDir,
isShipping,
}),
devServer: {
sockPath: `/${innerPort}/sockjs-node`,
sockPort: innerPort,
port: innerPort,
host: '0.0.0.0',
transportMode: 'ws',
hot: true,
liveReload: true,
disableHostCheck: true,
overlay: true,
writeToDisk: true,
contentBase: [`${appSettingDir}/../lib/assets`],
watchContentBase: true,
historyApiFallback: {
rewrites: getRewrites(appDefs),
},
headers: {
'Access-Control-Allow-Origin': '*', // 方便本机用localhost打开,难记ip
},
before: setupFilter(appDefs),
},
};
return result;
}
exports.getLocalIP = getLocalIP;
exports.getPixuiWebpackConfig = getPixuiWebpackConfig;
exports.setupFilter = setupFilter;