@sanpjs/bundler-webpack
Version:
@sanpjs/bundler-webpack
250 lines • 9.13 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
/**
* @file Sanp Webpack Bundler
* @author wangyongqing <wangyongqing01@baidu.com>
*/
const fs_extra_1 = __importDefault(require("fs-extra"));
const webpack_1 = __importDefault(require("webpack"));
const webpack_merge_1 = require("webpack-merge");
const tapable_1 = require("tapable");
const dotenv_1 = __importDefault(require("dotenv"));
const server_1 = __importDefault(require("@sanpjs/server"));
const rules = __importStar(require("./rules"));
const partials = __importStar(require("./partials"));
const utils_1 = require("./utils");
const inspect_1 = __importDefault(require("./inspect"));
const async_mfs_1 = __importDefault(require("./utils/async-mfs"));
class BundlerWebpack {
context;
hooks;
logger;
rawOptions;
running;
server;
useMfs;
mfs;
constructor(context) {
this.context = context;
this.running = false;
this.context.bundler = this;
this.hooks = Object.freeze({
config: new tapable_1.AsyncSeriesHook(['config']),
failed: new tapable_1.SyncHook(['error']),
done: new tapable_1.SyncHook(['stats'])
});
// 处理build options对应的finalize
this.logger = context.logger;
this.rawOptions = context.config;
this.useMfs = this.context.dev && this.context.server?.devMiddleware?.mfs;
if (this.useMfs) {
this.mfs = new async_mfs_1.default();
}
}
run(args, callback) {
if (this.running) {
return;
}
this.running = true;
const finalCallback = (err, stats) => {
// watch 模式下不改变 running
this.running = this.context.watch;
if (err) {
this.logger.error(err);
this.hooks.failed.call(err);
callback && callback(err);
return;
}
this.hooks.done.callAsync(stats, (err) => {
if (err) {
return this.hooks.failed.call(err);
}
callback && callback(null, stats);
});
};
this.toConfig(args, (err, config) => {
if (err || !config) {
return finalCallback(err);
}
// 最终调用 webpack 的地方
const compiler = (0, webpack_1.default)(config);
if (this.context.dev && config[0].devServer) {
if (this.useMfs) {
compiler.outputFileSystem = this.mfs;
}
this.server = new server_1.default(config[0].devServer, compiler, this.context);
return this.server.run(finalCallback);
}
compiler.run((err, stats) => {
if (err) {
return finalCallback(err);
}
if (stats && stats.hasErrors()) {
return finalCallback(stats.toJson().errors);
}
finalCallback(null, stats);
stats && (0, inspect_1.default)(stats, args, this);
});
// fixme: 此处调用会导致build立即结束
// this.hooks.compiler.callAsync(compiler, err => {
// if (err) {
// return finalCallback(err);
// }
// });
});
}
async close() {
if (this.running) {
this.server && (await this.server.close());
this.server = null;
this.running = false;
if (this.mfs) {
delete this.mfs.data;
delete this.mfs;
}
}
}
_loadPartial(name, args, callback) {
try {
const partial = partials[name];
callback(null, partial(this.context, args));
}
catch (e) {
// this.logger.error(`fail to load config partial ${name}`, e);
callback(e);
}
}
_loadEnv(mode) {
const load = (envPath) => {
let env = {};
try {
const content = fs_extra_1.default.readFileSync(envPath);
env = dotenv_1.default.parse(content) || {};
this.logger.debug('loadEnv envPath ', envPath);
this.logger.debug('loadEnv env object ', env);
}
catch (err) {
// 文件不存在
if (err.toString().indexOf('ENOENT') < 0) {
this.logger.error(err);
}
}
return env;
};
let env = {};
['.env', `.env.${mode}`, `.env.${mode}.local`].forEach(envName => {
const envPath = this.context.resolver.resolveEntry(envName, false);
if (fs_extra_1.default.existsSync(envPath)) {
env = Object.assign(env, load(envPath));
}
});
process.env.NODE_ENV = mode;
Object.keys(env).forEach(key => {
if (!process.env.hasOwnProperty(key)) {
process.env[key] = env[key];
}
});
}
normalizeConfig(args, callback) {
const { mode } = args;
let webpackConfig = {
// 此处注释后dev下不输出服务地址
// infrastructureLogging: {
// level: 'none'
// },
stats: 'errors-only'
};
const loadPartialCallback = (err, partialConfig) => {
if (err) {
return callback(err);
}
webpackConfig = (0, webpack_merge_1.merge)(webpackConfig, partialConfig);
};
// 遍历将 partials 中的配置合并到 webpackConfig 中
const partialTypes = ['base', mode];
for (let t of partialTypes) {
this._loadPartial(t, args, loadPartialCallback);
}
return webpackConfig;
}
normalizeSsrConfig(args, callback) {
const { mode } = args;
let webpackConfig = {
// 此处注释后dev下不输出服务地址
// infrastructureLogging: {
// level: 'none'
// },
stats: 'errors-only'
};
// 注入环境变量
this._loadEnv(mode);
const loadPartialCallback = (err, partialConfig) => {
if (err) {
return callback(err);
}
webpackConfig = (0, webpack_merge_1.merge)(webpackConfig, partialConfig);
};
// 遍历将 partials 中的配置合并到 webpackConfig 中
const partialTypes = ['server', mode];
for (let t of partialTypes) {
this._loadPartial(t, args, loadPartialCallback);
}
// TODO: 先跑通,后续独立搞一个 partials/server.ts 来处理 SSR 下的配置
webpackConfig.devtool = false;
webpackConfig.devServer = undefined;
return webpackConfig;
}
toConfig(args, callback) {
const context = this.context;
const { finalize } = context.config.build;
let webpackConfigs = [this.normalizeConfig(args, callback)];
// ssr 的打包处理
if (context.isSsr) {
webpackConfigs.push(this.normalizeSsrConfig(args, callback));
}
const internals = {
rules,
loader: utils_1.getLoader,
loaders: utils_1.getLoaders,
replaceOption: utils_1.replaceOption,
replaceRule: utils_1.replaceRule
};
// 调用 hooks.config 相关的 tapAsync 注册的方法
this.hooks.config.callAsync(webpackConfigs, (err) => {
if (err) {
return callback(err);
}
let configs = webpackConfigs;
// 通过 finalize 进行配置的终极处理
if (finalize) {
configs = webpackConfigs.map(webpackConfig => finalize(webpackConfig, internals, context));
}
callback(null, configs);
});
}
}
exports.default = BundlerWebpack;
;
//# sourceMappingURL=index.js.map