修改了webpack的初始化过程,增加了注入

This commit is contained in:
Xu Chang 2024-02-14 13:34:32 +08:00
parent 0ad9aa5e4a
commit 49454f9ee0
4 changed files with 338 additions and 204 deletions

View File

@ -81,7 +81,7 @@ const resolveModule = (resolveFn, filePath) => {
return resolveFn(`${filePath}.js`);
};
// config after eject: we're in ./config/
// config after eject: we're in ./web
module.exports = {
dotenv: resolveApp('.env'),
appPath: resolveApp('.'),

View File

@ -205,15 +205,20 @@ module.exports = function (webpackEnv) {
return loaders;
};
// 读取编译配置
const compilerConfigurationFile = path.join(paths.appRootPath, 'configuration', 'compiler.js');
const projectConfigration = fs.existsSync(compilerConfigurationFile) && require(compilerConfigurationFile).webpack;
const getOakInclude = () => {
return [
/oak-domain/,
/oak-external-sdk/,
const result = [
/oak-frontend-base/,
/oak-general-business/,
/oak-memory-tree-store/,
/oak-common-aspect/,
];
if (projectConfigration && projectConfigration.extraOakModules) {
result.push(...projectConfigration.extraOakModules);
}
return result;
};
return {
@ -255,14 +260,14 @@ module.exports = function (webpackEnv) {
// Point sourcemap entries to original disk location (format as URL on Windows)
devtoolModuleFilenameTemplate: isEnvProduction
? (info) =>
path
.relative(paths.appSrc, info.absoluteResourcePath)
.replace(/\\/g, '/')
path
.relative(paths.appSrc, info.absoluteResourcePath)
.replace(/\\/g, '/')
: isEnvDevelopment &&
((info) =>
path
.resolve(info.absoluteResourcePath)
.replace(/\\/g, '/')),
((info) =>
path
.resolve(info.absoluteResourcePath)
.replace(/\\/g, '/')),
},
cache: {
type: 'filesystem',
@ -369,18 +374,24 @@ module.exports = function (webpackEnv) {
},
},
resolve: {
fallback: {
crypto: require.resolve('crypto-browserify'),
buffer: require.resolve('safe-buffer'),
stream: require.resolve('stream-browserify'),
zlib: require.resolve('browserify-zlib'),
querystring: require.resolve('querystring-es3'),
url: false,
path: false,
fs: false,
net: false,
tls: false,
},
fallback: (() => {
const defaultFb = {
crypto: require.resolve('crypto-browserify'),
buffer: require.resolve('safe-buffer'),
stream: require.resolve('stream-browserify'),
zlib: require.resolve('browserify-zlib'),
querystring: require.resolve('querystring-es3'),
url: false,
path: false,
fs: false,
net: false,
tls: false,
}
if (projectConfigration && projectConfigration.resolve && projectConfigration.resolve.fallback) {
Object.assign(defaultFb, projectConfigration.resolve.fallback);
}
return defaultFb;
})(),
// This allows you to set a fallback for where webpack should look for modules.
// We placed these paths second because we want `node_modules` to "win"
// if there are any conflicts. This matches Node resolution mechanism.
@ -397,24 +408,25 @@ module.exports = function (webpackEnv) {
extensions: paths.moduleFileExtensions
.map((ext) => `.${ext}`)
.filter((ext) => useTypeScript || !ext.includes('ts')),
alias: {
// Support React Native Web
// https://www.smashingmagazine.com/2016/08/a-glimpse-into-the-future-with-react-native-for-web/
'react-native': 'react-native-web',
// Allows for better profiling with ReactDevTools
...(isEnvProductionProfile && {
'react-dom$': 'react-dom/profiling',
'scheduler/tracing': 'scheduler/tracing-profiling',
}),
...(modules.webpackAliases || {}),
'@': paths.appSrc,
'@project': paths.appRootSrc,
'@oak-general-business': paths.oakGeneralBusinessPath,
'@oak-frontend-base': paths.oakFrontendBasePath,
'@oak-app-domain': paths.oakAppDomainPath,
'bn.js': require.resolve('bn.js'),
assert: require.resolve('browser-assert'),
},
alias: (() => {
const defaultAlias = {
// Support React Native Web
// https://www.smashingmagazine.com/2016/08/a-glimpse-into-the-future-with-react-native-for-web/
'react-native': 'react-native-web',
// Allows for better profiling with ReactDevTools
...(isEnvProductionProfile && {
'react-dom$': 'react-dom/profiling',
'scheduler/tracing': 'scheduler/tracing-profiling',
}),
...(modules.webpackAliases || {}),
'bn.js': require.resolve('bn.js'),
assert: require.resolve('browser-assert'),
};
if (projectConfigration && projectConfigration.resolve && projectConfigration.resolve.alias) {
Object.assign(defaultAlias, projectConfigration.resolve.alias);
}
return defaultAlias;
})(),
plugins: [
// Prevents users from importing files from outside of src/ (or node_modules/).
// This often causes confusion because we only process files within src/ with babel.
@ -541,10 +553,10 @@ module.exports = function (webpackEnv) {
plugins: [
isEnvDevelopment &&
shouldUseReactRefresh &&
require.resolve(
'react-refresh/babel'
),
shouldUseReactRefresh &&
require.resolve(
'react-refresh/babel'
),
oakPathTsxPlugin,
oakRenderTsxPlugin,
// oakRouterPlugin,
@ -759,14 +771,14 @@ module.exports = function (webpackEnv) {
},
plugins: [
isEnvProduction &&
new CompressionWebpackPlugin({
filename: '[path][base].gz', //压缩后的文件名
algorithm: 'gzip', //压缩格式 有gzip、brotliCompress
test: /\.(js|css|svg)$/,
threshold: 10240, // 只处理比这个值大的资源,按字节算
minRatio: 0.8, //只有压缩率比这个值小的文件才会被处理,压缩率=压缩大小/原始大小,如果压缩后和原始文件大小没有太大区别,就不用压缩
deleteOriginalAssets: false, //是否删除原文件,最好不删除,服务器会自动优先返回同名的.gzip资源如果找不到还可以拿原始文件
}),
new CompressionWebpackPlugin({
filename: '[path][base].gz', //压缩后的文件名
algorithm: 'gzip', //压缩格式 有gzip、brotliCompress
test: /\.(js|css|svg)$/,
threshold: 10240, // 只处理比这个值大的资源,按字节算
minRatio: 0.8, //只有压缩率比这个值小的文件才会被处理,压缩率=压缩大小/原始大小,如果压缩后和原始文件大小没有太大区别,就不用压缩
deleteOriginalAssets: false, //是否删除原文件,最好不删除,服务器会自动优先返回同名的.gzip资源如果找不到还可以拿原始文件
}),
// Generates an `index.html` file with the <script> injected.
new HtmlWebpackPlugin(
Object.assign(
@ -777,19 +789,19 @@ module.exports = function (webpackEnv) {
},
isEnvProduction
? {
minify: {
removeComments: true,
collapseWhitespace: true,
removeRedundantAttributes: true,
useShortDoctype: true,
removeEmptyAttributes: true,
removeStyleLinkTypeAttributes: true,
keepClosingSlash: true,
minifyJS: true,
minifyCSS: true,
minifyURLs: true,
},
}
minify: {
removeComments: true,
collapseWhitespace: true,
removeRedundantAttributes: true,
useShortDoctype: true,
removeEmptyAttributes: true,
removeStyleLinkTypeAttributes: true,
keepClosingSlash: true,
minifyJS: true,
minifyCSS: true,
minifyURLs: true,
},
}
: undefined
)
),
@ -797,10 +809,10 @@ module.exports = function (webpackEnv) {
// a network request.
// https://github.com/facebook/create-react-app/issues/5358
isEnvProduction &&
shouldInlineRuntimeChunk &&
new InlineChunkHtmlPlugin(HtmlWebpackPlugin, [
/runtime-.+[.]js/,
]),
shouldInlineRuntimeChunk &&
new InlineChunkHtmlPlugin(HtmlWebpackPlugin, [
/runtime-.+[.]js/,
]),
// Makes some environment variables available in index.html.
// The public URL is available as %PUBLIC_URL% in index.html, e.g.:
// <link rel="icon" href="%PUBLIC_URL%/favicon.ico">
@ -819,22 +831,22 @@ module.exports = function (webpackEnv) {
// Experimental hot reloading for React .
// https://github.com/facebook/react/tree/main/packages/react-refresh
isEnvDevelopment &&
shouldUseReactRefresh &&
new ReactRefreshWebpackPlugin({
overlay: false,
}),
shouldUseReactRefresh &&
new ReactRefreshWebpackPlugin({
overlay: false,
}),
// Watcher doesn't work well if you mistype casing in a path so we use
// a plugin that prints an error when you attempt to do this.
// See https://github.com/facebook/create-react-app/issues/240
isEnvDevelopment && new CaseSensitivePathsPlugin(),
isEnvProduction &&
new MiniCssExtractPlugin({
// Options similar to the same options in webpackOptions.output
// both options are optional
filename: 'static/css/[name].[contenthash:8].css',
chunkFilename:
'static/css/[name].[contenthash:8].chunk.css',
}),
new MiniCssExtractPlugin({
// Options similar to the same options in webpackOptions.output
// both options are optional
filename: 'static/css/[name].[contenthash:8].css',
chunkFilename:
'static/css/[name].[contenthash:8].chunk.css',
}),
// Generate an asset manifest file with the following content:
// - "files" key: Mapping of all asset filenames to their corresponding
// output file so that tools can pick it up without having to parse
@ -871,136 +883,136 @@ module.exports = function (webpackEnv) {
// Generate a service worker script that will precache, and keep up to date,
// the HTML & assets that are part of the webpack build.
isEnvProduction &&
fs.existsSync(swSrc) &&
new WorkboxWebpackPlugin.InjectManifest({
swSrc,
dontCacheBustURLsMatching: /\.[0-9a-f]{8}\./,
exclude: [/\.map$/, /asset-manifest\.json$/, /LICENSE/],
// Bump up the default maximum size (2mb) that's precached,
// to make lazy-loading failure scenarios less likely.
// See https://github.com/cra-template/pwa/issues/13#issuecomment-722667270
maximumFileSizeToCacheInBytes: 5 * 1024 * 1024,
}),
fs.existsSync(swSrc) &&
new WorkboxWebpackPlugin.InjectManifest({
swSrc,
dontCacheBustURLsMatching: /\.[0-9a-f]{8}\./,
exclude: [/\.map$/, /asset-manifest\.json$/, /LICENSE/],
// Bump up the default maximum size (2mb) that's precached,
// to make lazy-loading failure scenarios less likely.
// See https://github.com/cra-template/pwa/issues/13#issuecomment-722667270
maximumFileSizeToCacheInBytes: 5 * 1024 * 1024,
}),
// TypeScript type checking
useTypeScript &&
new ForkTsCheckerWebpackPlugin({
async: isEnvDevelopment,
typescript: {
typescriptPath: resolve.sync('typescript', {
basedir: paths.appNodeModules,
}),
// configOverwrite: {
// compilerOptions: {
// sourceMap: isEnvProduction
// ? shouldUseSourceMap
// : isEnvDevelopment,
// skipLibCheck: true,
// inlineSourceMap: false,
// declarationMap: false,
// noEmit: true,
// incremental: true,
// tsBuildInfoFile: paths.appTsBuildInfoFile,
// },
// },
configFile: paths.appTsConfig,
context: paths.appRootPath,
diagnosticOptions: {
syntactic: true,
},
mode: 'write-references',
// profile: true,
memoryLimit,
new ForkTsCheckerWebpackPlugin({
async: isEnvDevelopment,
typescript: {
typescriptPath: resolve.sync('typescript', {
basedir: paths.appNodeModules,
}),
// configOverwrite: {
// compilerOptions: {
// sourceMap: isEnvProduction
// ? shouldUseSourceMap
// : isEnvDevelopment,
// skipLibCheck: true,
// inlineSourceMap: false,
// declarationMap: false,
// noEmit: true,
// incremental: true,
// tsBuildInfoFile: paths.appTsBuildInfoFile,
// },
// },
configFile: paths.appTsConfig,
context: paths.appRootPath,
diagnosticOptions: {
syntactic: true,
},
issue: {
// This one is specifically to match during CI tests,
// as micromatch doesn't match
// '../cra-template-typescript/template/src/App.tsx'
// otherwise.
include: [
{ file: '../**/app/**/*.{ts,tsx}' },
{ file: '**/app/**/*.{ts,tsx}' },
{ file: '../**/app/**/*.*.{ts,tsx}' },
{ file: '**/app/**/*.*.{ts,tsx}' },
{ file: '../**/src/**/*.{ts,tsx}' },
{ file: '**/src/**/*.{ts,tsx}' },
{ file: '../**/src/**/*.*.{ts,tsx}' },
{ file: '**/src/**/*.*.{ts,tsx}' },
],
exclude: [
{ file: '**/src/**/__tests__/**' },
{ file: '**/src/**/?(*.){spec|test}.*' },
{ file: '**/src/setupProxy.*' },
{ file: '**/src/setupTests.*' },
],
},
logger: {
log: (message) => console.log(message),
error: (message) => console.error(message),
},
}),
mode: 'write-references',
// profile: true,
memoryLimit,
},
issue: {
// This one is specifically to match during CI tests,
// as micromatch doesn't match
// '../cra-template-typescript/template/src/App.tsx'
// otherwise.
include: [
{ file: '../**/app/**/*.{ts,tsx}' },
{ file: '**/app/**/*.{ts,tsx}' },
{ file: '../**/app/**/*.*.{ts,tsx}' },
{ file: '**/app/**/*.*.{ts,tsx}' },
{ file: '../**/src/**/*.{ts,tsx}' },
{ file: '**/src/**/*.{ts,tsx}' },
{ file: '../**/src/**/*.*.{ts,tsx}' },
{ file: '**/src/**/*.*.{ts,tsx}' },
],
exclude: [
{ file: '**/src/**/__tests__/**' },
{ file: '**/src/**/?(*.){spec|test}.*' },
{ file: '**/src/setupProxy.*' },
{ file: '**/src/setupTests.*' },
],
},
logger: {
log: (message) => console.log(message),
error: (message) => console.error(message),
},
}),
!disableESLintPlugin &&
new ESLintPlugin({
// Plugin options
extensions: ['js', 'mjs', 'jsx', 'ts', 'tsx'],
formatter: require.resolve(
'react-dev-utils/eslintFormatter'
),
eslintPath: require.resolve('eslint'),
failOnError: !(isEnvDevelopment && emitErrorsAsWarnings),
context: paths.appSrc,
cache: true,
cacheLocation: path.resolve(
paths.appNodeModules,
'.cache/.eslintcache'
),
// ESLint class options
cwd: paths.appPath,
resolvePluginsRelativeTo: __dirname,
baseConfig: {
extends: [
require.resolve('eslint-config-react-app/base'),
],
rules: {
...(!hasJsxRuntime && {
'react/react-in-jsx-scope': 'error',
}),
},
new ESLintPlugin({
// Plugin options
extensions: ['js', 'mjs', 'jsx', 'ts', 'tsx'],
formatter: require.resolve(
'react-dev-utils/eslintFormatter'
),
eslintPath: require.resolve('eslint'),
failOnError: !(isEnvDevelopment && emitErrorsAsWarnings),
context: paths.appSrc,
cache: true,
cacheLocation: path.resolve(
paths.appNodeModules,
'.cache/.eslintcache'
),
// ESLint class options
cwd: paths.appPath,
resolvePluginsRelativeTo: __dirname,
baseConfig: {
extends: [
require.resolve('eslint-config-react-app/base'),
],
rules: {
...(!hasJsxRuntime && {
'react/react-in-jsx-scope': 'error',
}),
},
}),
},
}),
new webpack.ProvidePlugin({
Buffer: ['buffer', 'Buffer'],
}),
shouldAnalyze &&
new BundleAnalyzerPlugin({
// 可以是`server``static`或`disabled`。
// 在`server`模式下分析器将启动HTTP服务器来显示软件包报告。
// 在“静态”模式下会生成带有报告的单个HTML文件。
// 在`disabled`模式下,你可以使用这个插件来将`generateStatsFile`设置为`true`来生成Webpack Stats JSON文件。
analyzerMode: 'server',
// 将在“服务器”模式下使用的主机启动HTTP服务器。
analyzerHost: '127.0.0.1',
// 将在“服务器”模式下使用的端口启动HTTP服务器。
analyzerPort: 8888,
// 路径捆绑,将在`static`模式下生成的报告文件。
// 相对于捆绑输出目录。
reportFilename: 'report.html',
// 模块大小默认显示在报告中。
// 应该是`stat``parsed`或者`gzip`中的一个。
// 有关更多信息,请参见“定义”一节。
defaultSizes: 'parsed',
// 在默认浏览器中自动打开报告
openAnalyzer: true,
// 如果为true则Webpack Stats JSON文件将在bundle输出目录中生成
generateStatsFile: false,
// 如果`generateStatsFile`为`true`将会生成Webpack Stats JSON文件的名字。
// 相对于捆绑输出目录。
statsFilename: 'stats.json',
// stats.toJson方法的选项。
// 例如,您可以使用`sourcefalse`选项排除统计文件中模块的来源。
// 在这里查看更多选项https //github.com/webpack/webpack/blob/webpack-1/lib/Stats.js#L21
statsOptions: null,
logLevel: 'info',
}),
new BundleAnalyzerPlugin({
// 可以是`server``static`或`disabled`。
// 在`server`模式下分析器将启动HTTP服务器来显示软件包报告。
// 在“静态”模式下会生成带有报告的单个HTML文件。
// 在`disabled`模式下,你可以使用这个插件来将`generateStatsFile`设置为`true`来生成Webpack Stats JSON文件。
analyzerMode: 'server',
// 将在“服务器”模式下使用的主机启动HTTP服务器。
analyzerHost: '127.0.0.1',
// 将在“服务器”模式下使用的端口启动HTTP服务器。
analyzerPort: 8888,
// 路径捆绑,将在`static`模式下生成的报告文件。
// 相对于捆绑输出目录。
reportFilename: 'report.html',
// 模块大小默认显示在报告中。
// 应该是`stat``parsed`或者`gzip`中的一个。
// 有关更多信息,请参见“定义”一节。
defaultSizes: 'parsed',
// 在默认浏览器中自动打开报告
openAnalyzer: true,
// 如果为true则Webpack Stats JSON文件将在bundle输出目录中生成
generateStatsFile: false,
// 如果`generateStatsFile`为`true`将会生成Webpack Stats JSON文件的名字。
// 相对于捆绑输出目录。
statsFilename: 'stats.json',
// stats.toJson方法的选项。
// 例如,您可以使用`sourcefalse`选项排除统计文件中模块的来源。
// 在这里查看更多选项https //github.com/webpack/webpack/blob/webpack-1/lib/Stats.js#L21
statsOptions: null,
logLevel: 'info',
}),
].filter(Boolean),
// Turn off performance processing because we utilize
// our own hints via the FileSizeReporter

View File

@ -0,0 +1,12 @@
// import { CompilerConfiguration } from 'oak-domain/lib/types/Configuration';
const path = require('path');
module.exports = {
webpack: {
resolve: {
alias: {
'@project': path.resolve('src'),
'@oak-app-domain': path.resolve('src', 'oak-app-domain'),
}
}
}
};

View File

@ -0,0 +1,110 @@
import { EntityDict } from '@project/oak-app-domain';
import { Feature } from 'oak-frontend-base';
import { CommonAspectDict } from 'oak-common-aspect';
import { AspectDict } from '../aspects/AspectDict';
import { BackendRuntimeContext } from '@project/context/BackendRuntimeContext';
import { FrontendRuntimeContext } from '@project/context/FrontendRuntimeContext';
import { groupBy } from 'oak-domain/lib/utils/lodash';
import { ContextMenuFactory } from 'oak-frontend-base/es/features/contextMenuFactory';
import Console from './Console';
type GroupName = 'System';
type Groups = {
icon: string;
name: GroupName;
}[];
interface IMenu<T extends keyof EntityDict> {
name: string;
icon: string;
url: string;
entity?: T;
paths?: string[];
action?: EntityDict[T]['Action'];
parent?: GroupName;
};
export interface OMenu {
name: GroupName | string;
icon: string;
url?: string;
children?: Array<OMenu>;
};
const groups: Groups = [
{
name: 'System', // 系统级别配置
icon: 'setup_fill',
},
];
const menus: IMenu<keyof EntityDict>[] = [
{
name: 'Dashboard',
icon: 'document',
url: '',
},
{
name: 'relationManage',
icon: 'share',
url: '/relation/entityList',
parent: 'System',
entity: 'relation',
action: 'create',
paths: [],
},
];
export default class Menu extends Feature {
private contextMenuFactory: ContextMenuFactory<EntityDict,
BackendRuntimeContext,
FrontendRuntimeContext,
AspectDict & CommonAspectDict<EntityDict, BackendRuntimeContext>>;
private console: Console;
private menus?: OMenu[];
constructor(
contextMenuFactory: ContextMenuFactory<EntityDict,
BackendRuntimeContext,
FrontendRuntimeContext,
AspectDict & CommonAspectDict<EntityDict, BackendRuntimeContext>>,
console: Console
) {
super();
this.contextMenuFactory = contextMenuFactory;
this.contextMenuFactory.setMenus(menus);
this.console = console;
this.console.subscribe(
() => {
this.refreshMenus();
}
);
}
refreshMenus() {
const roomId = this.console.getRoomId();
const menus = this.contextMenuFactory.getMenusByContext<IMenu<keyof EntityDict>>('room', roomId);
const menuGroup = groupBy(menus, 'parent');
this.menus = (menus as any[]).filter(ele => !ele.parent).concat(
groups.map((ele) => {
const { name, icon } = ele;
const children = menuGroup[name];
return {
name,
icon,
children,
};
}).filter((ele) => !!ele.children)
);
this.publish();
}
getMenus() {
if (!this.menus) {
this.refreshMenus();
}
return this.menus;
}
}