dn-middleware-webpack5
Version:
--- group: middleware name: webpack5 title: Webpack5 ---
255 lines (220 loc) • 8.47 kB
text/typescript
import * as fs from "fs";
import * as path from "path";
import * as Dawn from "@dawnjs/types";
import * as assert from "assert";
import { camelCase } from "lodash";
import { Env, IGetWebpackConfigOpts, IOpts } from "../types";
import { formatNullStringToList, formatReglikeObject, getExistFile } from "./utils";
// 生成排除配置
function makeExternal(commonjs: string, root: string, amd?: string) {
amd = amd || commonjs;
const commonjs2 = commonjs;
return { commonjs, commonjs2, root, amd };
}
// 库默认排除设定
const LIB_DEFAULT_EXTERNALS = {
jquery: makeExternal("jquery", "jQuery"),
zepto: makeExternal("zepto", "Zepto"),
react: makeExternal("react", "React"),
"react-dom": makeExternal("react-dom", "ReactDOM"),
};
// 普通项目默认排除设定
const PRO_DEFAULT_EXTERNALS = {
jquery: "jQuery",
zepto: "Zepto",
react: "React",
"react-dom": "ReactDOM",
};
// Validate and format input opts
const formatAndValidateOpts = (opts: Partial<IOpts>, ctx: Dawn.Context) => {
const options = Object.assign({}, opts) as IGetWebpackConfigOpts;
// cwd
options.cwd = options.cwd || ctx.cwd;
// env
const isLegalEnv = (e?: Env) => ["development", "production"].includes(e);
if (!isLegalEnv(options.env)) {
let envMessage = "[webpack5] `env` should be one of development/production";
if (isLegalEnv(process.env?.DN_ENV as Env)) {
options.env = process.env.DN_ENV as Env;
envMessage += `, set to "${options.env}" automatically by DN_ENV`;
} else if (isLegalEnv(process.env?.NODE_ENV as Env)) {
options.env = process.env.NODE_ENV as Env;
envMessage += `, set to "${options.env}" automatically by NODE_ENV`;
} else {
// ctx.command == current pipe full-name: init/dev/build/publish/..
options.env = ctx.command.includes("dev") || ctx.command.includes("daily") ? "development" : "production";
envMessage += `, set to "${options.env}" automatically by DN_CMD`;
}
ctx.console.warn(envMessage);
}
// entry
if (
!opts.entry ||
(Array.isArray(opts.entry) && !opts.entry?.length) ||
(typeof opts.entry === "object" && !Object.keys(opts.entry)?.length)
) {
opts.entry = getExistFile({
cwd: options.cwd,
files: ["src/index.tsx", "src/index.ts", "src/index.jsx", "src/index.js"],
returnRelative: true,
});
}
assert.ok(opts.entry, "[webpack5] No `entry` found, checkout guide for usage details.");
options.entry = formatReglikeObject(opts.entry as any);
// template
if (opts.template === true || opts.template === undefined) {
// auto find templates
opts.template = getExistFile({
cwd: options.cwd,
// `src/assets/index.html` is not recommanded and will be removed soon
files: ["public/index.html", "src/assets/index.html"],
returnRelative: true,
});
options.template = formatReglikeObject(opts.template);
} else if (
!opts.template ||
(Array.isArray(opts.template) && !opts.template?.length) ||
(typeof opts.template === "object" && !Object.keys(opts.template)?.length)
) {
options.template = [];
} else {
options.template = formatReglikeObject(opts.template);
}
// watch
// default true when in development
options.watch = options.watch ?? false /*options.env === "development"*/;
// watchOpts
options.watchOpts = options.watchOpts ?? {
// aggregateTimeout: 200,
poll: true,
ignored: /node_modules/,
};
// injectCss
// default: only inject when dev, not inject when build
ctx.injectCSS = options.injectCSS === undefined ? options.env === "development" : !!options.injectCSS;
// publicPath
ctx.publicPath = options.publicPath ?? "";
// devtool/sourceMap
options.devtool = options.devtool ?? options.sourceMap;
// inject & append
options.inject = formatNullStringToList(options.inject);
options.append = formatNullStringToList(options.append);
// output
const defaultOutputPath = "./build";
if (typeof options.output === "string") {
options.output = { path: options.output };
} else if (!options.output || !options.output?.path) {
options.output = { ...options?.output, path: defaultOutputPath };
}
if (options.output?.libraryTarget === "umd") {
options.output.library = options.output.library || (camelCase(path.basename(ctx.project.name)) as any);
}
// folders
options.folders = {
script: options?.folders?.js ?? ".",
style: options?.folders?.css ?? ".",
media: options?.folders?.img ?? "assets",
html: options?.folders?.html ?? "",
...options.folders,
};
// chunkFilename for v3
if (options.chunkFilename) {
options.output.chunkFilename = options.chunkFilename;
ctx.console.warn(
"[webpack5] `chunkFilename` is not recommanded in dn-middleware-webpack5, please use `output.chunkFilename` instead",
);
}
options.hot = options.hot ?? (options.env === "development" && options.watch);
assert.ok(
!(options.hot && options.env === "production"),
"[webpack5] react-refresh must be disabled in production mode",
);
// externals
if (typeof options.external !== "boolean") {
// auto disable external when dev mode
options.external = options.env === "development" ? false : true;
}
if (options.external === false) {
options.externals = {};
} else {
if (options.hot && options.externals) {
options.externals = {};
ctx.console.warn(
"[webpack5] Auto set `externals` to {} by using react-refresh in development mode. You can set `hot` to false to disabled it in development mode",
);
}
options.externals = options.externals || (options.output?.library ? LIB_DEFAULT_EXTERNALS : PRO_DEFAULT_EXTERNALS);
}
// performance
// default is false
// true means warning
options.performance = options.performance === true ? "warning" : options.performance ?? false;
options.performanceConfig = { hints: options.performance };
// compress
// default is true when in production
// Make sure to turn off this when you need dn-middleware-compress
options.compress = options.compress ?? options.env === "production";
// target
// browser means web
// default is web
options.target = options.target === "browser" ? "web" : options.target ?? "web";
// alias
if (ctx.useTypeScript && options.alias) {
ctx.console.warn("[webpack5] `alias` is not recommanded in ts project, please use paths in tsconfig.json");
}
// common chunk
options.common = {
disabled: true, // break change: default disabled common
name: "common",
...options.common,
};
// analysis
options.analysis = options.analysis ?? false;
if (options.analysis === true) {
// set default analysisConfig
options.analysis = {
analyzerMode: "server",
openAnalyzer: false,
};
}
// profiling
// TODO: change default to true?
options.profiling = options.profiling ?? false;
// cache default filesystem
options.cache = options.cache ?? {
type: "memory",
};
// cssLoader
// style.module.(css|less|scss|sass)$
options.cssLoader = options.cssLoader ?? {};
if (options.cssModules) {
options.cssLoader = {
modules: opts.cssModules,
// CamelCase is not supported anymore. It's recommended for local selectors.
// camelCase: opts.cssModules, // 只要启用就采用「小驼峰」
};
}
// tscCompileOnError
// default is true
// If `true`, errors in TypeScript type checking will not prevent start script from running app,
// and will not cause build script to exit unsuccessfully.
// Also downgrades all TypeScript type checking error messages to warning messages.
options.tscCompileOnError = options.tscCompileOnError ?? true;
options.disabledTypeCheck = options.disabledTypeCheck ?? false;
// other judges
// useTypeScript judge by entry file ext
ctx.useTypeScript = options.entry?.some?.(({ file }) => file.endsWith(".ts") || file.endsWith(".tsx"));
assert.ok(
// if entry is dot ts(x), but not found tsconfig.json, exist
!(ctx.useTypeScript && !fs.existsSync(path.join(ctx.cwd, "tsconfig.json"))),
"[webpack5] Your entry is typescript but missing tsconfig.json file.",
);
// $config
options.config = {
name: options.config?.name ?? "$config",
path: options.config?.name ?? "./src/config",
env: options.config?.env || ctx.env || ctx.command,
};
return options as IOpts;
};
export default formatAndValidateOpts;