fec-builder
Version:
通用的前端构建工具,屏蔽业务无关的细节配置,开箱即用
130 lines (129 loc) • 6.1 kB
JavaScript
/**
* @file serve as dev server
* @author nighca <nighca@live.cn>
*/
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const fs_1 = __importDefault(require("fs"));
const url_1 = __importDefault(require("url"));
const webpack_1 = __importDefault(require("webpack"));
const webpack_dev_server_1 = __importDefault(require("webpack-dev-server"));
const logger_1 = __importDefault(require("./utils/logger"));
const utils_1 = require("./utils");
const webpack_2 = require("./webpack");
const build_conf_1 = require("./utils/build-conf");
const lodash_1 = require("lodash");
const paths_1 = require("./utils/paths");
// 业务项目的配置文件,变更时需要重启 server
const projectConfigFiles = [
'tsconfig.json'
];
function serve(port) {
return __awaiter(this, void 0, void 0, function* () {
let stopDevServer = yield runDevServer(port);
function restartDevServer() {
return __awaiter(this, void 0, void 0, function* () {
yield (stopDevServer === null || stopDevServer === void 0 ? void 0 : stopDevServer());
stopDevServer = yield runDevServer(port);
});
}
const disposers = [];
disposers.push(build_conf_1.watchBuildConfig(() => __awaiter(this, void 0, void 0, function* () {
logger_1.default.info('Detected build config change, restarting server...');
restartDevServer();
})));
projectConfigFiles.forEach(file => {
const filePath = paths_1.abs(file);
if (fs_1.default.existsSync(filePath)) {
disposers.push(utils_1.watchFile(filePath, () => __awaiter(this, void 0, void 0, function* () {
logger_1.default.info(`Detected ${file} change, restarting server...`);
restartDevServer();
})));
}
});
process.on('exit', () => {
disposers.forEach(disposer => disposer());
});
});
}
function runDevServer(port) {
return __awaiter(this, void 0, void 0, function* () {
const buildConfig = yield build_conf_1.findBuildConfig();
const webpackConfig = yield webpack_2.getServeConfig();
logger_1.default.debug('webpack config:', webpackConfig);
const devServerConfig = {
hotOnly: true,
// 方便开发调试
disableHostCheck: true,
// devServer 中的 public 字段会被拿去计算得到 hot module replace 相关请求的 URI
// 这里用 0.0.0.0:0 可以让插到页面的 client 脚本自动依据 window.location 去获得 host
// 从而正确地建立 hot module replace 依赖的 ws 链接及其它请求,逻辑见:
// 这里之所以要求使用页面的 window.location 信息,是因为 builder 在容器中 serve 时端口会被转发,
// 即可能配置 port 为 80,在(宿主机)浏览器中通过 8080 端口访问
public: '0.0.0.0:0',
publicPath: utils_1.getPathFromUrl(buildConfig.publicUrl),
stats: 'errors-only',
proxy: getProxyConfig(buildConfig.devProxy),
historyApiFallback: {
rewrites: getHistoryApiFallbackRewrites(buildConfig)
}
};
const compiler = webpack_1.default(webpackConfig);
const server = new webpack_dev_server_1.default(compiler, devServerConfig);
const host = '0.0.0.0';
server.listen(port, host, () => {
logger_1.default.info(`Server started on ${host}:${port}`);
});
return () => new Promise(resolve => {
server.close(resolve);
});
});
}
exports.default = utils_1.logLifecycle('Serve', serve, logger_1.default);
const defaultProxyConfig = {
changeOrigin: true,
onProxyReq(proxyReq) {
// add header `X-Real-IP`
const origin = proxyReq.getHeader('origin');
if (origin) {
proxyReq.setHeader("X-Real-IP", url_1.default.parse(origin).hostname);
}
// fix `referer` to avoid csrf detect
const referer = proxyReq.getHeader('referer');
if (referer) {
proxyReq.setHeader('referer', referer.replace(url_1.default.parse(referer).host, proxyReq.getHeader('host')));
}
},
onProxyRes(proxyRes) {
// 干掉 set-cookie 中的 secure 设置,因为本地开发 server 是 http 的
// TODO: 考虑支持 https dev server?
if (proxyRes.headers['set-cookie']) {
proxyRes.headers['set-cookie'] = proxyRes.headers['set-cookie'].map(cookie => cookie.replace('; Secure', ''));
}
}
};
function getProxyConfig(devProxy) {
return lodash_1.mapValues(devProxy, target => (Object.assign(Object.assign({}, defaultProxyConfig), { target })));
}
// get rewrites for devServerConfig.historyApiFallback
function getHistoryApiFallbackRewrites(buildConfig) {
const prefix = utils_1.getPathFromUrl(buildConfig.publicUrl, false);
return lodash_1.entries(buildConfig.pages).map(([name, { path }]) => ({
from: new RegExp(path),
to: '/' + (prefix
? `${prefix}/${utils_1.getPageFilename(name)}`
: utils_1.getPageFilename(name))
}));
}
;