instapack
Version:
All-in-one TypeScript and Sass compiler for web applications!
357 lines (356 loc) • 13.4 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.TypeScriptBuildEngine = void 0;
const path = require("path");
const fse = require("fs-extra");
const chalk = require("chalk");
const portfinder = require("portfinder");
const TypeScript = require("typescript");
const webpack = require("webpack");
const clean_webpack_plugin_1 = require("clean-webpack-plugin");
const webpack_plugin_serve_1 = require("webpack-plugin-serve");
const ReactRefreshWebpackPlugin = require("@pmmmwh/react-refresh-webpack-plugin");
const webpackPluginServeClientJS = require.resolve('webpack-plugin-serve/client');
const reactRefreshBabelPluginJS = require.resolve('react-refresh/babel');
const babelPluginDynamicImportJS = require.resolve('@babel/plugin-syntax-dynamic-import');
const resolveFrom_1 = require("./importers/resolveFrom");
const Shout_1 = require("./Shout");
const PathFinder_1 = require("./variables-factory/PathFinder");
const LoaderPaths_1 = require("./loaders/LoaderPaths");
const TypescriptConfigParser_1 = require("./TypescriptConfigParser");
const InstapackBuildPlugin_1 = require("./plugins/InstapackBuildPlugin");
const TypeScriptPathsTranslator_1 = require("./TypeScriptPathsTranslator");
const UserSettingsPath_1 = require("./user-settings/UserSettingsPath");
class TypeScriptBuildEngine {
constructor(variables) {
this.useBabel = false;
this.port = 28080;
this.certificates = null;
this.variables = variables;
this.finder = new PathFinder_1.PathFinder(variables);
this.typescriptCompilerOptions = TypescriptConfigParser_1.parseTypescriptConfig(variables.root, variables.typescriptConfiguration).options;
if (!this.typescriptCompilerOptions.target) {
Shout_1.Shout.warning('instapack does not support targeting ES3! JS build target has been set to ES5. (TypeScript compiler options)');
this.typescriptCompilerOptions.target = TypeScript.ScriptTarget.ES5;
}
this.typescriptCompilerOptions.noEmit = false;
this.typescriptCompilerOptions.emitDeclarationOnly = false;
this.typescriptCompilerOptions.sourceMap = variables.sourceMap;
this.typescriptCompilerOptions.inlineSources = variables.sourceMap;
}
get jsBabelWebpackRules() {
return {
test: /\.jsx?$/,
exclude: /node_modules/,
use: {
loader: LoaderPaths_1.LoaderPaths.babel,
ident: 'babel-js'
}
};
}
get typescriptWebpackRules() {
const loaders = [{
loader: LoaderPaths_1.LoaderPaths.typescript,
ident: 'typescript',
options: {
compilerOptions: this.typescriptCompilerOptions
}
}];
if (this.useBabel) {
loaders.unshift({
loader: LoaderPaths_1.LoaderPaths.babel,
ident: 'babel-typescript'
});
}
const tsRules = {
test: /\.tsx?$/,
exclude: /node_modules/,
use: loaders
};
return tsRules;
}
get vueWebpackRules() {
return {
test: /\.vue$/,
exclude: /node_modules/,
use: [{
loader: this.vueLoaderPath,
ident: 'vue',
options: {
transformAssetUrls: {}
}
}]
};
}
get htmlWebpackRules() {
return {
test: /\.html?$/,
exclude: /node_modules/,
use: [{
loader: LoaderPaths_1.LoaderPaths.html,
ident: 'html-txt'
}]
};
}
get vueCssWebpackRules() {
const vueStyleLoader = {
loader: LoaderPaths_1.LoaderPaths.vueStyle,
ident: 'vue-style'
};
const cssModulesLoader = {
loader: LoaderPaths_1.LoaderPaths.css,
ident: 'vue-css-module',
options: {
esModule: false,
modules: {
localIdentName: '[local]_[contenthash:8]'
},
url: false
}
};
const cssLoader = {
loader: LoaderPaths_1.LoaderPaths.css,
ident: 'vue-css',
options: {
esModule: false,
url: false
}
};
return {
test: /\.css$/,
resourceQuery: /\?vue/,
oneOf: [
{
resourceQuery: /module=true/,
use: [vueStyleLoader, cssModulesLoader]
},
{
use: [vueStyleLoader, cssLoader]
}
]
};
}
get reactRefreshWebpackRules() {
return {
test: /\.[jt]sx?$/,
exclude: /node_modules/,
use: [{
loader: LoaderPaths_1.LoaderPaths.babel,
ident: 'react-fast-refresh-babel',
options: {
plugins: [
babelPluginDynamicImportJS,
reactRefreshBabelPluginJS,
]
}
}]
};
}
get transpileLibrariesWebpackRules() {
return {
test: /\.js$/,
include: /node_modules/,
use: {
loader: LoaderPaths_1.LoaderPaths.transpileLibraries,
ident: 'js-libraries-to-es5',
options: {
compilerOptions: this.typescriptCompilerOptions
}
}
};
}
get webpackPlugins() {
var _a;
const plugins = [];
const typescriptTarget = (_a = this.typescriptCompilerOptions.target) !== null && _a !== void 0 ? _a : TypeScript.ScriptTarget.ES3;
plugins.push(new InstapackBuildPlugin_1.InstapackBuildPlugin(this.variables, typescriptTarget));
if (this.vueLoader) {
plugins.push(new this.vueLoader.VueLoaderPlugin());
}
if (Object.keys(this.variables.env).length > 0) {
plugins.push(new webpack.EnvironmentPlugin(this.variables.env));
}
if (this.variables.serve) {
plugins.push(new webpack_plugin_serve_1.WebpackPluginServe({
host: 'localhost',
port: this.port,
https: this.certificates,
progress: 'minimal',
log: {
level: 'error'
}
}));
}
if (this.variables.reactRefresh) {
plugins.push(new ReactRefreshWebpackPlugin());
}
if (this.variables.production) {
plugins.push(new clean_webpack_plugin_1.CleanWebpackPlugin());
}
return plugins;
}
get webpackRules() {
const rules = [
this.typescriptWebpackRules,
];
if (this.vueLoaderPath) {
rules.push(this.vueCssWebpackRules);
rules.push(this.vueWebpackRules);
}
rules.push(this.htmlWebpackRules);
if (this.useBabel) {
rules.push(this.jsBabelWebpackRules);
}
if (this.typescriptCompilerOptions.target === TypeScript.ScriptTarget.ES5) {
rules.push(this.transpileLibrariesWebpackRules);
}
if (this.variables.reactRefresh) {
rules.unshift(this.reactRefreshWebpackRules);
}
return rules;
}
get webpackConfigurationDevTool() {
if (this.variables.sourceMap === false) {
return false;
}
if (this.variables.production) {
return 'hidden-source-map';
}
if (this.variables.watch === false) {
return 'source-map';
}
return 'eval-source-map';
}
createWebpackConfiguration() {
var _a;
const entry = {
main: {
filename: this.finder.jsOutputFileName,
import: [this.finder.jsEntry],
}
};
if (this.variables.serve) {
entry.main.import.push(webpackPluginServeClientJS);
}
const config = {
entry: entry,
output: {
filename: this.finder.jsChunkFileName,
path: path.normalize(this.finder.jsOutputFolder),
publicPath: 'js/',
library: this.variables.namespace,
libraryTarget: (this.variables.umdLibraryProject ? 'umd' : undefined)
},
externals: this.variables.externals,
resolve: this.webpackResolveOptions,
plugins: this.webpackPlugins,
module: {
rules: this.webpackRules
},
mode: (this.variables.production ? 'production' : 'development'),
devtool: this.webpackConfigurationDevTool,
optimization: {
emitOnErrors: false,
},
performance: {
hints: false
}
};
const tsTarget = (_a = this.typescriptCompilerOptions.target) !== null && _a !== void 0 ? _a : TypeScript.ScriptTarget.ES5;
if (tsTarget < TypeScript.ScriptTarget.ES2015) {
config.target = ['web', 'es5'];
}
if (!this.variables.umdLibraryProject && config.optimization) {
config.optimization.splitChunks = {
cacheGroups: {
vendors: {
name: 'dll',
test: /[\\/]node_modules[\\/]/,
chunks: 'initial',
enforce: true,
priority: 99
}
}
};
}
return config;
}
get webpackResolveOptions() {
const alias = TypeScriptPathsTranslator_1.mergeTypeScriptPathAlias(this.typescriptCompilerOptions, this.finder.root, this.variables.alias);
const wildcards = TypeScriptPathsTranslator_1.getWildcardModules(this.typescriptCompilerOptions, this.finder.root);
if (this.variables.reactRefresh) {
alias['react-refresh'] = [path.resolve(__dirname, '../node_modules/react-refresh')];
}
const config = {
extensions: ['.ts', '.tsx', '.js', '.jsx', '.vue', '.json'],
alias: alias
};
if (wildcards) {
config.modules = wildcards;
}
if (this.typescriptCompilerOptions.preserveSymlinks) {
config.symlinks = false;
}
return config;
}
buildOnce(webpackConfiguration) {
return new Promise((ok, reject) => {
webpack(webpackConfiguration, (err, stats) => {
if (err) {
reject(err);
}
ok(stats);
});
});
}
watch(webpackConfiguration) {
const compiler = webpack(webpackConfiguration);
return new Promise((ok, reject) => {
compiler.watch({
ignored: ['node_modules'],
aggregateTimeout: 300
}, (err) => {
if (err) {
reject(err);
}
return ok();
});
});
}
async build() {
this.useBabel = await fse.pathExists(this.finder.babelConfiguration);
if (this.variables.vue) {
const vueLoaderPath = await resolveFrom_1.resolveFrom('vue-loader', this.variables.root);
if (vueLoaderPath) {
this.vueLoaderPath = vueLoaderPath;
this.vueLoader = require(vueLoaderPath);
}
}
if (this.variables.serve) {
this.port = await portfinder.getPortPromise({
port: this.port
});
const host = `${this.variables.https ? 'https' : 'http'}://localhost:${chalk.greenBright(this.port)}`;
Shout_1.Shout.timed(chalk.yellowBright('Hot Reload'), `server running on ${host}`);
}
if (this.variables.https) {
const certFileAsync = fse.readFile(UserSettingsPath_1.UserSettingsPath.certFile);
const keyFileAsync = fse.readFile(UserSettingsPath_1.UserSettingsPath.keyFile);
this.certificates = {
key: await keyFileAsync,
cert: await certFileAsync
};
}
const webpackConfiguration = this.createWebpackConfiguration();
if (this.variables.watch) {
await this.watch(webpackConfiguration);
}
else {
const stats = await this.buildOnce(webpackConfiguration);
if (this.variables.stats && stats) {
await fse.outputJson(this.finder.statsJsonFilePath, stats.toJson());
}
}
}
}
exports.TypeScriptBuildEngine = TypeScriptBuildEngine;