Browse Source

Pull request #2930: KERNEL-12095 新增webpack worker打包插件

Merge in VISUAL/fineui from ~TELLER/fineui:master to master

* commit '0038b76320079328d69d27e34e87981d414d05af':
  feat: 更新下脚本
  refactor: 改个名字
  KERNEL-12095 fix: 封装new部分的代码
  refactor: 加个默认值
  KERNEL-12095 chore: 新增webpack worker打包插件
es6
Teller 2 years ago
parent
commit
3185e3111e
  1. 2
      .npmignore
  2. 29
      bin/cli/worker/cli.worker.js
  3. 0
      bin/cli/worker/template/action_type.ts
  4. 13
      bin/cli/worker/template/main_thread/action/action.worker_ability_test.t
  5. 25
      bin/cli/worker/template/main_thread/main_thread.t
  6. 5
      bin/cli/worker/template/main_thread_template.ts
  7. 8
      bin/cli/worker/template/utils/action_type.t
  8. 13
      bin/cli/worker/template/utils/payload_type.t
  9. 24
      bin/cli/worker/template/worker_thread/action/action.worker_ability_test.t
  10. 12
      bin/cli/worker/template/worker_thread/worker_thread.t
  11. 5
      bin/cli/worker/template/worker_thread_template.ts
  12. 4
      examples/worker_new/index.js
  13. 4
      examples/worker_new/worker.js
  14. 9
      plugins/webpack-fui-worker-plugin/constants.js
  15. 45
      plugins/webpack-fui-worker-plugin/index.js
  16. 109
      plugins/webpack-fui-worker-plugin/worker-loader.js
  17. 8
      typescript/core/worker/worker.main_thread.ts
  18. 9
      typescript/core/worker/worker.worker_thread.ts
  19. 13
      typescript/core/worker/workers.ts

2
.npmignore

@ -40,3 +40,5 @@
!dist/2.0/bi.min.css !dist/2.0/bi.min.css
!bin/* !bin/*
!bin/**/* !bin/**/*
!plugins/*
!plugins/**/*

29
bin/cli/worker/cli.worker.js

@ -1,17 +1,25 @@
const fs = require('fs'); const fs = require('fs');
const path = require('path'); const path = require('path');
function scanAndCreate(structure, root = process.cwd()) { function first2UpperCase(str) {
return str.toLowerCase().replace(/( |^)[a-z]/g, L => L.toUpperCase());
}
function scanAndCreate(structure, workerName, root = process.env.INIT_CWD) {
Object.keys(structure) Object.keys(structure)
.forEach(name => { .forEach(name => {
if (typeof structure[name] === 'object') { if (typeof structure[name] === 'object') {
fs.mkdirSync(path.resolve(root, name)); fs.mkdirSync(path.resolve(root, name));
scanAndCreate(structure[name], path.resolve(root, `./${name}`)); scanAndCreate(structure[name], workerName, path.resolve(root, `./${name}`));
} else if (structure[name] === '') { } else if (structure[name] === '') {
fs.appendFileSync(path.resolve(root, name), ''); fs.appendFileSync(path.resolve(root, name), '');
} else if (typeof structure[name] === 'string') { } else if (typeof structure[name] === 'string') {
const content = fs.readFileSync(structure[name]).toString(); let content = fs.readFileSync(structure[name]).toString();
content = content
.replace(/\${WorkerName}/g, first2UpperCase(workerName))
.replace(/\${workerName}/g, workerName);
fs.appendFileSync(path.resolve(root, name), content); fs.appendFileSync(path.resolve(root, name), content);
} }
@ -33,15 +41,20 @@ module.exports = {
const structure = { const structure = {
[`${name}_worker`]: { [`${name}_worker`]: {
'main_thread': { 'main_thread': {
action: {}, action: {
[`${name}_main_thread.ts`]: path.resolve(__dirname, './template/main_thread_template.ts'), 'action.worker_ability_test.ts': path.resolve(__dirname, './template/main_thread/action/action.worker_ability_test.t'),
},
[`${name}_main_thread.ts`]: path.resolve(__dirname, './template/main_thread/main_thread.t'),
}, },
utils: { utils: {
'action_type.ts': '', 'action_type.ts': path.resolve(__dirname, './template/utils/action_type.t'),
'payload_type.ts': path.resolve(__dirname, './template/utils/payload_type.t'),
}, },
'worker_thread': { 'worker_thread': {
action: {}, action: {
[`${name}_worker_thread.ts`]: path.resolve(__dirname, './template/worker_thread_template.ts'), 'action.worker_ability_test.ts': path.resolve(__dirname, './template/worker_thread/action/action.worker_ability_test.t'),
},
[`${name}_worker_thread.ts`]: path.resolve(__dirname, './template/worker_thread/worker_thread.t'),
}, },
}, },
}; };

0
bin/cli/worker/template/action_type.ts

13
bin/cli/worker/template/main_thread/action/action.worker_ability_test.t

@ -0,0 +1,13 @@
import { WorkerAbilityTestActionType } from '../../utils/action_type';
import { WorkerAbilityTestPayload, WorkerAbilityTestReponse } from '../../utils/payload_type';
export class WorkerAbilityTestMainThreadAction extends BI.Workers.WorkerBaseAction {
/**
* 通信能力检测
*/
public communicationTest(): Promise<WorkerAbilityTestReponse['CommunicationTest']> {
const mainThreadPostTime: WorkerAbilityTestPayload['CommunicationTest'] = Date.now();
return this.controller.requestPromise(WorkerAbilityTestActionType.CommunicationTest, mainThreadPostTime);
}
}

25
bin/cli/worker/template/main_thread/main_thread.t

@ -0,0 +1,25 @@
import { WorkerAbilityTestMainThreadAction } from './action/action.worker_ability_test';
// 不需要一起打包的话则不需要引入这行
import { workerUrl } from 'fui-worker!../worker_thread/${workerName}_worker_thread';
class ${WorkerName}MainTreadWorker extends BI.Workers.MainThreadWorker {
private communicationTest: WorkerAbilityTestMainThreadAction;
public initActions(): void {
this.communicationTest = this.createAction(WorkerAbilityTestMainThreadAction);
}
public testCommunication() {
return this.communicationTest.communicationTest();
}
}
const ${workerName}MainTreadWorker = BI.Workers.createWorker(${WorkerName}MainTreadWorker, {
workerUrl,
workerName: BI.UUID(),
});
${workerName}MainTreadWorker.testCommunication()
.then(v => {
console.log(v);
});

5
bin/cli/worker/template/main_thread_template.ts

@ -1,5 +0,0 @@
class CrudMainTreadWorker extends BI.Workers.MainThreadWorker {
protected initActions(): void {
// to init some actions
}
}

8
bin/cli/worker/template/utils/action_type.t

@ -0,0 +1,8 @@
/*
* Worker 事务标识
* 每类事务有命名空间, 包含多个具体事务
*/
export const enum WorkerAbilityTestActionType {
CommunicationTest = 'CommunicationTest',
}

13
bin/cli/worker/template/utils/payload_type.t

@ -0,0 +1,13 @@
/**
* 跨线程通信各事务的发送数据类型声明
*/
export interface WorkerAbilityTestPayload {
CommunicationTest: number;
}
/**
* 跨线程通信各事务的响应数据类型声明
*/
export interface WorkerAbilityTestReponse {
CommunicationTest: number;
}

24
bin/cli/worker/template/worker_thread/action/action.worker_ability_test.t

@ -0,0 +1,24 @@
import { WorkerAbilityTestActionType } from '../../utils/action_type';
import { WorkerAbilityTestPayload, WorkerAbilityTestReponse } from '../../utils/payload_type';
export class WorkerAbilityTestWorkerThreadAction extends BI.Workers.WorkerBaseAction {
protected addActionHandler(): void {
this.controller.addActionHandler(
WorkerAbilityTestActionType.CommunicationTest,
this.communicationTest.bind(this)
);
}
/**
* 通信能力检测的处理器
*/
private communicationTest(
payload: WorkerAbilityTestPayload['CommunicationTest']
): WorkerAbilityTestReponse['CommunicationTest'] {
const mainThreadPostTime = payload;
// 收到主线程信息的耗时
const workerGetMessageDuration = Date.now() - mainThreadPostTime;
return workerGetMessageDuration;
}
}

12
bin/cli/worker/template/worker_thread/worker_thread.t

@ -0,0 +1,12 @@
// TODO: 这边需要先import fineui资源
import { WorkerAbilityTestWorkerThreadAction } from './action/action.worker_ability_test';
class ${WorkerName}WorkerTreadWorker extends BI.Workers.MainThreadWorker {
public communicationTest: WorkerAbilityTestWorkerThreadAction;
public initActions(): void {
this.communicationTest = this.createAction(WorkerAbilityTestWorkerThreadAction);
}
}
export const ${workerName}WorkerTreadWorker = BI.Workers.createWorker(${WorkerName}WorkerTreadWorker);

5
bin/cli/worker/template/worker_thread_template.ts

@ -1,5 +0,0 @@
class CrudWorkerTreadWorker extends BI.Workers.MainThreadWorker {
protected initActions(): void {
// to init some actions
}
}

4
examples/worker_new/index.js

@ -42,7 +42,7 @@ const HeartBeatCheckAction = BI.inherit(BI.Workers.WorkerBaseAction, {
} }
}); });
var WorkerThreadWorker = BI.inherit(BI.Workers.MainThreadWorker, { var MainThreadWorker = BI.inherit(BI.Workers.MainThreadWorker, {
initActions: function() { initActions: function() {
this.cookieAction = this.createAction(CookieAction); this.cookieAction = this.createAction(CookieAction);
@ -64,7 +64,7 @@ var WorkerThreadWorker = BI.inherit(BI.Workers.MainThreadWorker, {
} }
}); });
var mainThreadWorker = new WorkerThreadWorker({ var mainThreadWorker = BI.Workers.createWorker(MainThreadWorker, {
workerUrl: "./worker_new/worker.js", workerUrl: "./worker_new/worker.js",
workerName: "demo" workerName: "demo"
}); });

4
examples/worker_new/worker.js

@ -58,7 +58,7 @@ const HeartBeatCheckAction = BI.inherit(BI.Workers.WorkerBaseAction, {
} }
}); });
var WorkerThreadWorker = BI.inherit(BI.Workers.WorkerThreadWorker, { var MainThreadWorker = BI.inherit(BI.Workers.WorkerThreadWorker, {
initActions: function() { initActions: function() {
this.cookieAction = this.createAction(CookieAction); this.cookieAction = this.createAction(CookieAction);
@ -75,6 +75,6 @@ var WorkerThreadWorker = BI.inherit(BI.Workers.WorkerThreadWorker, {
} }
}); });
var workerThreadWorker = new WorkerThreadWorker(); var workerThreadWorker = BI.Workers.createWorker(MainThreadWorker);
workerThreadWorker.fetchCookie(); workerThreadWorker.fetchCookie();

9
plugins/webpack-fui-worker-plugin/constants.js

@ -0,0 +1,9 @@
const WorkerPluginName = 'FuiWorkerPlugin';
const WorkerLoaderName = 'FuiWorkerWorkerLoader';
const FileNamePrefix = 'worker-';
module.exports = {
WorkerPluginName,
WorkerLoaderName,
FileNamePrefix,
};

45
plugins/webpack-fui-worker-plugin/index.js

@ -0,0 +1,45 @@
/*
* worker-plugin
*/
const path = require('path');
const webpack = require('webpack');
const { WorkerPluginName } = require('./constants');
class FuiWorkerPlugin {
constructor(options = {}) {
this.options = options;
}
apply(compiler) {
// 为主线程构建添加 __WORKER__ 环境变量, 构建中区分不同线程源码, 实现代码拆减
compiler.hooks.afterPlugins.tap(WorkerPluginName, compiler => {
new webpack.DefinePlugin({
// __WORKER__ 表示当前所在线程是否是 worker 线程
// 主线程构建中为 false
__WORKER__: false,
}).apply(compiler);
});
// 添加自定义的worker entry-loader
compiler.hooks.afterResolvers.tap(WorkerPluginName, compiler => {
/**
* https://webpack.js.org/configuration/resolve/#resolveloader
* 使用 resolveloader 添加自定义的 worker loader
*/
if (!compiler.options.resolveLoader) {
compiler.options.resolveLoader = {
alias: {},
};
}
if (!compiler.options.resolveLoader.alias) {
compiler.options.resolveLoader.alias = {};
}
// 动态添加 worker 的 worker-loader, 命名为 "fui-worker"
compiler.options.resolveLoader.alias['fui-worker'] = path.resolve(__dirname, './worker-loader.js');
});
}
}
module.exports = FuiWorkerPlugin;

109
plugins/webpack-fui-worker-plugin/worker-loader.js

@ -0,0 +1,109 @@
/*
* fui-worker worker-loader
*/
const webpack = require('webpack');
const loaderUtils = require('loader-utils');
const SingleEntryPlugin = require('webpack/lib/SingleEntryPlugin');
const { WorkerLoaderName, FileNamePrefix } = require('./constants');
// 正常 loader 处理逻辑
function loader() {
const callback = this.async();
this.cacheable(false);
// 过滤掉当前的 worker-loader, 保留 worker 侧构建需要的其他 loader(babel-loader/ts-loader 等)
const otherLoaders = this.loaders.filter((loader, index) => {
if (index === this.loaderIndex) {
return false;
}
return true;
});
/**
* 拼接构建需要的 loader 字符串, 用于指定 childCompiler 的构建 loader
* 比如: /path/to/babel-loader/lib/index.js!/path/to/ts-loader/index.js!
*/
const loaderPath = otherLoaders.reduce((pre, loader) => `${pre}${loader.path}!`, '');
/**
* worker 独立构建的 entry
* 构建 loader + worker 源码入口文件路径
*
* https://webpack.js.org/concepts/loaders/#inline
* `!!` 实现在 childCompiler 中忽略其他所有 loader, 只保留主构建的 loader
* 不然 worker 入口在 childCompiler 中会继续由 worker-loader 处理, 造成死循环
*/
const workerEntry = `!!${loaderPath}${this.resourcePath}`;
// 把资源纳入构建流程的依赖, 实现 dev 模式下的 watch
this.addDependency(workerEntry);
// 生成的 service 独立 bundle 名称
const entryFileName = `${FileNamePrefix}index`;
// 获取传递给 loader 的 options
loaderUtils.getOptions(this) || {};
// 创建 childCompiler, 用于实现 worker 构建为独立 js 资源
const childCompiler = this._compilation.createChildCompiler(WorkerLoaderName, {
globalObject: 'this',
});
childCompiler.context = this._compiler.context;
// 指定独立构建的 entry 和生成 js 资源名称
new SingleEntryPlugin(this.context, workerEntry, entryFileName).apply(childCompiler);
// 设置 worker 侧的环境变量
new webpack.DefinePlugin({
__WORKER__: true,
}).apply(childCompiler);
// 添加 window 全局对象, 映射为 worker 线程全局对象 self
// 如果在 worker 源码中添加, 可能没有前置到所有引用模块前
new webpack.BannerPlugin({
banner: 'self.window = self;',
raw: true,
entryOnly: true,
}).apply(childCompiler);
const subCache = `subcache ${__dirname} ${workerEntry}`;
childCompiler.hooks.compilation.tap(WorkerLoaderName, compilation => {
if (compilation.cache) {
if (!compilation.cache[subCache]) compilation.cache[subCache] = {};
compilation.cache = compilation.cache[subCache];
}
});
childCompiler.runAsChild((error, entries, compilation) => {
if (!error && compilation.errors && compilation.errors.length) {
// eslint-disable-next-line no-param-reassign
error = compilation.errors[0];
}
// compatible with Array (v4) and Set (v5) prototypes
const entry = entries && entries[0] && entries[0].files.values().next().value;
if (!error && !entry) {
// eslint-disable-next-line no-param-reassign
error = Error(`${WorkerLoaderName}, no entry for ${workerEntry}`);
}
if (error) {
return callback(error);
}
return callback(
null,
// 插入代码的转译和压缩由主构建配置的 babel/ts loader 处理, 不需要 worker-worker 来处理
// 添加 @ts-nocheck 避免 ts-check 报错
`// @ts-nocheck
const servicePath = __webpack_public_path__ + ${JSON.stringify(entry)};
export const workerUrl = servicePath;
`
);
});
return;
}
module.exports = loader;

8
typescript/core/worker/worker.main_thread.ts

@ -5,7 +5,7 @@ import { IWorkerOptions } from "./worker.core";
/** /**
* 线Worker * 线Worker
*/ */
export abstract class MainThreadWorker { export class MainThreadWorker {
/** /**
* Worker * Worker
*/ */
@ -24,10 +24,12 @@ export abstract class MainThreadWorker {
public constructor(options: IWorkerOptions) { public constructor(options: IWorkerOptions) {
this.name = options.workerName; this.name = options.workerName;
this.controller = new WorkerMainThreadController(options); this.controller = new WorkerMainThreadController(options);
this.initActions();
} }
protected abstract initActions(): void; /**
* actions
*/
public initActions() {}
/** /**
* worker * worker

9
typescript/core/worker/worker.worker_thread.ts

@ -4,7 +4,7 @@ import { WorkerThreadController } from "./controller/worker.worker_thread.contro
/** /**
* worker线程实例 * worker线程实例
*/ */
export abstract class WorkerThreadWorker { export class WorkerThreadWorker {
/** /**
* Worker 线 * Worker 线
*/ */
@ -12,11 +12,12 @@ export abstract class WorkerThreadWorker {
public constructor() { public constructor() {
this.controller = new WorkerThreadController(); this.controller = new WorkerThreadController();
this.initActions();
} }
protected abstract initActions(): void; /**
* actions
*/
public initActions() {}
/** /**
* action * action

13
typescript/core/worker/workers.ts

@ -1,12 +1,22 @@
import { WorkerChannel } from "./worker.channel"; import { WorkerChannel } from "./worker.channel";
import { WorkerBaseController } from "./controller/worker.controller"; import { WorkerBaseController } from "./controller/worker.controller";
import { WorkerMessageType } from "./worker.core"; import { IWorkerOptions, WorkerMessageType } from "./worker.core";
import { WorkerMainThreadController } from "./controller/worker.main_thread.controller"; import { WorkerMainThreadController } from "./controller/worker.main_thread.controller";
import { WorkerThreadController } from "./controller/worker.worker_thread.controller"; import { WorkerThreadController } from "./controller/worker.worker_thread.controller";
import { WorkerBaseAction } from "./action/worker.action"; import { WorkerBaseAction } from "./action/worker.action";
import { MainThreadWorker } from "./worker.main_thread"; import { MainThreadWorker } from "./worker.main_thread";
import { WorkerThreadWorker } from "./worker.worker_thread"; import { WorkerThreadWorker } from "./worker.worker_thread";
function createWorker<T extends typeof MainThreadWorker>(ThreadWorker: T, options: IWorkerOptions): InstanceType<T>
function createWorker<T extends typeof WorkerThreadWorker>(ThreadWorker: T): InstanceType<T>
function createWorker<T extends typeof MainThreadWorker | typeof WorkerThreadWorker>(ThreadWorker: T, options?: IWorkerOptions): InstanceType<T> {
const threadWorker = new ThreadWorker(options as any) as InstanceType<T>;
threadWorker.initActions();
return threadWorker;
}
export const Workers = { export const Workers = {
WorkerChannel, WorkerChannel,
WorkerBaseController, WorkerBaseController,
@ -16,4 +26,5 @@ export const Workers = {
MainThreadWorker, MainThreadWorker,
WorkerThreadWorker, WorkerThreadWorker,
WorkerMessageType, WorkerMessageType,
createWorker,
}; };

Loading…
Cancel
Save