casterly
Version:
CLI for Casterly
536 lines (535 loc) • 24.9 kB
JavaScript
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 });
const fs_1 = require("fs");
const path_1 = __importDefault(require("path"));
const utils_1 = require("@casterly/utils");
const react_refresh_webpack_plugin_1 = __importDefault(require("@pmmmwh/react-refresh-webpack-plugin"));
// @ts-ignore: typings not up-to-date
const case_sensitive_paths_webpack_plugin_1 = __importDefault(require("case-sensitive-paths-webpack-plugin"));
const chalk_1 = __importDefault(require("chalk"));
const fork_ts_checker_webpack_plugin_1 = __importDefault(require("fork-ts-checker-webpack-plugin"));
// @ts-ignore
const mini_css_extract_plugin_1 = __importDefault(require("mini-css-extract-plugin"));
const semver_1 = __importDefault(require("semver"));
const webpack_1 = __importDefault(require("webpack"));
const log_1 = require("../output/log");
const Log = __importStar(require("../output/log"));
const dependencies_1 = require("../utils/dependencies");
const filterBoolean_1 = require("../utils/filterBoolean");
const resolveRequest_1 = __importDefault(require("../utils/resolveRequest"));
const constants_1 = require("./constants");
const env_1 = __importDefault(require("./env"));
const paths_1 = __importDefault(require("./paths"));
const userConfig_1 = __importDefault(require("./userConfig"));
const optimization_1 = require("./webpack/optimization");
const SSRImportPlugin_1 = __importDefault(require("./webpack/plugins/SSRImportPlugin"));
const RoutesManifestPlugin_1 = require("./webpack/plugins/routes/RoutesManifestPlugin");
const styles_1 = require("./webpack/styles");
const loadPostcssPlugins = async (dir) => {
const postcssRc = userConfig_1.default.postcssRc;
if (postcssRc == null) {
return [
require.resolve('postcss-flexbugs-fixes'),
[
require.resolve('postcss-preset-env'),
{
autoprefixer: {
flexbox: 'no-2009',
},
stage: 3,
},
],
];
}
if (Object.keys(postcssRc).some((key) => key !== 'plugins')) {
Log.warn('You PostCSS configuration export unknown attributes.' +
' Please remove the following fields to suppress this' +
' warning: ' +
Object.keys(postcssRc)
.filter((key) => key !== 'plugins')
.join(', '));
}
let plugins = postcssRc.plugins;
if (!Array.isArray(plugins)) {
const pluginsObject = plugins;
plugins = Object.keys(plugins).reduce((acc, pluginName) => {
const pluginOptions = pluginsObject[pluginName];
if (typeof pluginOptions === 'undefined') {
throw new Error(`Your PostCSS configuration is invalid, the configuration for the plugin ${pluginName} must not be undefined.`);
}
acc.push([pluginName, pluginOptions]);
return acc;
}, []);
}
plugins = plugins
.map((plugin) => {
if (plugin == null) {
Log.warn(chalk_1.default `A {yellow null} PostCSS plugin was provided. This entry will be ignored.`);
return false;
}
if (typeof plugin === 'string') {
return [plugin, true];
}
if (Array.isArray(plugin)) {
const [pluginName, pluginConfig] = plugin;
if (typeof pluginName === 'string' &&
(typeof pluginConfig === 'boolean' ||
typeof pluginConfig === 'object')) {
return [pluginName, pluginConfig];
}
if (typeof pluginName !== 'string') {
Log.error(chalk_1.default `A PostCSS plugin must be provided as a {bold 'string'}. Instead we got: '${pluginName}'.`);
}
else {
Log.error(chalk_1.default `A PostCSS plugin was passed as an array but did not provide it's configuration object ('${pluginName}').`);
}
return false;
}
if (typeof plugin === 'function') {
Log.error(chalk_1.default `A PostCSS plugin was passed as a function using require(), but it must be provided as a {bold string}.`);
return false;
}
Log.error(chalk_1.default `An unknown PostCSS plugin was provided (${plugin}).`);
return false;
})
.filter((value) => value !== false)
.map(([pluginName, pluginConfig]) => {
const resolvedPluginPath = require.resolve(pluginName, { paths: [dir] });
return [resolvedPluginPath, pluginConfig];
});
const resolvedPlugins = await Promise.all(plugins.map(([plugin, options]) => {
if (options === false) {
return false;
}
if (options == null) {
throw new Error(chalk_1.default `A {chalk null} PostCSS plugin option was provided (${plugin}).`);
}
if (options === true) {
return require(plugin);
}
const keys = Object.keys(options);
if (keys.length === 0) {
return require(plugin);
}
return require(plugin)(options);
}));
return resolvedPlugins.filter((plugin) => plugin !== false);
};
const not = (fn) => (...args) => !fn(...args);
const isRouteManifestChildCompiler = (name) => name === RoutesManifestPlugin_1.CHILD_COMPILER_NAME;
const getBaseWebpackConfig = async (options) => {
var _a, _b, _c, _d;
const { isServer = false, dev = false, profile = false, configFn } = options !== null && options !== void 0 ? options : {};
// Get environment variables to inject into our app.
const env = env_1.default({ isServer });
const sassLoaderConfig = {
loader: require.resolve('sass-loader'),
options: {
implementation: require('sass'),
sassOptions: {
fiber: require('fibers'),
},
},
};
const postcssPlugins = await loadPostcssPlugins(paths_1.default.appPath);
const cssConfig = styles_1.getStyleLoaders({ dev, isServer, postcssPlugins });
const cssModuleConfig = styles_1.getStyleLoaders({
dev,
isServer,
cssModules: true,
postcssPlugins,
});
const sassConfig = styles_1.getStyleLoaders({
dev,
isServer,
loaders: [sassLoaderConfig],
postcssPlugins,
});
const sassModuleConfig = styles_1.getStyleLoaders({
dev,
isServer,
cssModules: true,
loaders: [sassLoaderConfig],
postcssPlugins,
});
const cssRules = [
{
test: styles_1.cssGlobalRegex,
use: cssConfig,
compiler: not(isRouteManifestChildCompiler),
},
{
test: styles_1.cssRegex,
exclude: [styles_1.cssGlobalRegex, /node_modules/],
use: cssModuleConfig,
compiler: not(isRouteManifestChildCompiler),
},
{
test: styles_1.cssRegex,
include: [{ not: [paths_1.default.appSrc] }],
use: cssConfig,
compiler: not(isRouteManifestChildCompiler),
},
{
test: styles_1.sassGlobalRegex,
use: sassConfig,
compiler: not(isRouteManifestChildCompiler),
},
{
test: styles_1.sassRegex,
exclude: [styles_1.sassGlobalRegex, /node_modules/],
use: sassModuleConfig,
compiler: not(isRouteManifestChildCompiler),
},
{
test: styles_1.sassRegex,
include: [{ not: [paths_1.default.appSrc] }],
use: sassConfig,
compiler: not(isRouteManifestChildCompiler),
},
{
test: [styles_1.sassRegex, styles_1.cssRegex],
use: ['ignore-loader'],
compiler: isRouteManifestChildCompiler,
},
];
const routesManifestPluginInstance = !isServer
? new RoutesManifestPlugin_1.RoutesManifestPlugin()
: null;
const dir = paths_1.default.appBuildFolder;
const outputDir = isServer ? 'server' : '';
const outputPath = path_1.default.join(dir, outputDir);
const webpackMode = dev ? 'development' : 'production';
const reactVersion = await dependencies_1.getDependencyVersion('react');
const hasJsxRuntime = reactVersion != null &&
semver_1.default.satisfies((_a = semver_1.default.coerce(reactVersion)) !== null && _a !== void 0 ? _a : '-1', '^16.4.0 || >=17 || ~0.0.0');
const typescriptPath = require.resolve('typescript', {
paths: [paths_1.default.appNodeModules],
});
const useTypescript = !!typescriptPath && (await utils_1.fileExists(paths_1.default.appTsConfig));
const esbuildForDependencies = (_c = (_b = userConfig_1.default.userConfig.experiments) === null || _b === void 0 ? void 0 : _b.esbuildDependencies) !== null && _c !== void 0 ? _c : userConfig_1.default.defaultConfig.experiments.esbuildDependencies;
const chunkFilename = dev ? '[name]' : '[name].[contenthash]';
const extractedCssFilename = dev ? '[name]' : '[name].[contenthash:8]';
const externals = isServer
? [
({ context, request }, callback) => {
const excludedModules = [
// add modules that should be transpiled here
];
if (!request || excludedModules.indexOf(request) !== -1) {
return callback();
}
let res;
try {
// Resolve the import with the webpack provided context, this
// ensures we're resolving the correct version when multiple
// exist.
res = resolveRequest_1.default(request, `${context}/`);
}
catch (_) {
// If the request cannot be resolved, we need to tell webpack to
// "bundle" it so that webpack shows an error (that it cannot be
// resolved).
return callback();
}
// Same as above, if the request cannot be resolved we need to have
// webpack "bundle" it so it surfaces the not found error.
if (!res) {
callback();
}
// Bundled Node.js code is relocated without its node_modules tree.
// This means we need to make sure its request resolves to the same
// package that'll be available at runtime. If it's not identical,
// we need to bundle the code (even if it _should_ be external).
let baseRes;
try {
baseRes = resolveRequest_1.default(request, `${dir}/`);
}
catch (_) {
// ignore me
}
// Same as above: if the package, when required from the root,
// would be different from what the real resolution would use, we
// cannot externalize it.
if (!baseRes ||
(baseRes !== res &&
// if res and baseRes are symlinks they could point to the the same file
fs_1.realpathSync(baseRes) !== fs_1.realpathSync(res))) {
return callback();
}
if (res.match(/casterly[/\\]lib[/\\]/)) {
return callback();
}
// Webpack itself has to be compiled because it doesn't always use module relative paths
if (res.match(/node_modules[/\\]webpack/) ||
res.match(/node_modules[/\\]css-loader/)) {
return callback();
}
// Anything else that is standard JavaScript within `node_modules`
// can be externalized.
if (res.match(/node_modules[/\\].*\.js$/)) {
return callback(undefined, `commonjs ${request}`);
}
// Default behavior: bundle the code!
callback();
},
]
: undefined;
const entrypoints = {
[constants_1.STATIC_ENTRYPOINTS_ROUTES]: paths_1.default.appRoutesJs,
};
const serverEntry = (await utils_1.fileExists(paths_1.default.appServerEntry))
? paths_1.default.appServerEntry
: paths_1.default.serverDefaultAppServer;
const browserEntry = (await utils_1.fileExists(paths_1.default.appBrowserEntry))
? paths_1.default.appBrowserEntry
: paths_1.default.serverDefaultAppBrowser;
const appSrcFiles = [paths_1.default.appSrc, serverEntry, browserEntry];
let config = {
mode: webpackMode,
name: isServer ? 'server' : 'client',
target: isServer ? 'node' : 'web',
devtool: dev && !isServer ? 'eval-source-map' : !isServer ? 'source-map' : false,
bail: webpackMode === 'production',
context: paths_1.default.appPath,
externals,
cache: dev
? {
type: 'filesystem',
allowCollectingMemory: true,
cacheDirectory: path_1.default.join(paths_1.default.appBuildFolder, 'cache'),
buildDependencies: {
config: [
__filename,
path_1.default.join(paths_1.default.appPath, utils_1.constants.WEBPACK_CONFIG_FILE),
],
},
version: isServer ? 'server' : 'client',
}
: false,
entry: () => (Object.assign(Object.assign({}, entrypoints), (!isServer
? Object.assign({ [constants_1.STATIC_RUNTIME_MAIN]: browserEntry }, (dev ? { [constants_1.STATIC_RUNTIME_HOT]: paths_1.default.serverClientHot } : null)) : { [constants_1.STATIC_RUNTIME_MAIN]: serverEntry }))),
watchOptions: {
ignored: [
'**/.git/**',
'**/node_modules/**',
'**/' +
((_d = userConfig_1.default.userConfig.buildFolder) !== null && _d !== void 0 ? _d : userConfig_1.default.defaultConfig.buildFolder) +
'/**',
],
},
output: {
publicPath: '/_casterly/',
path: outputPath,
filename: isServer
? '[name].js'
: `${constants_1.STATIC_CHUNKS_PATH}/[name]${dev ? '' : '-[chunkhash]'}.js`,
chunkFilename: isServer
? `${chunkFilename}.js`
: `${constants_1.STATIC_CHUNKS_PATH}/${chunkFilename}.js`,
assetModuleFilename: isServer
? '[hash][ext][query]'
: (pathData) => {
var _a;
const ext = ((_a = pathData.filename) === null || _a === void 0 ? void 0 : _a.split('?')[0].match(new RegExp(`\\.(${useTypescript
? paths_1.default.typescriptFileExtensions.join('|') + '|'
: ''}${paths_1.default.moduleFileExtensions.join('|')})$`)))
? '.js'
: '[ext]';
return `${constants_1.STATIC_ASSETS_PATH}/[hash]${ext}[query]`;
},
hotUpdateMainFilename: `${constants_1.STATIC_WEBPACK_PATH}/[fullhash].hot-update.json`,
hotUpdateChunkFilename: `${constants_1.STATIC_WEBPACK_PATH}/[id].[fullhash].hot-update.js`,
devtoolModuleFilenameTemplate: (info) => path_1.default.resolve(info.absoluteResourcePath).replace(/\\/g, '/'),
library: isServer ? undefined : '_RS',
libraryTarget: isServer ? 'commonjs2' : 'assign',
},
performance: false,
optimization: optimization_1.createOptimizationConfig({
dev,
isServer,
getNumberOfRoutes: () => { var _a; return (_a = routesManifestPluginInstance === null || routesManifestPluginInstance === void 0 ? void 0 : routesManifestPluginInstance.getNumberOfRoutes()) !== null && _a !== void 0 ? _a : 0; },
}),
resolveLoader: {
alias: ['custom-babel-loader'].reduce((alias, loader) => {
// using multiple aliases to replace `resolveLoader.modules`
alias[loader] = path_1.default.join(__dirname, 'webpack', 'loaders', loader);
return alias;
}, {}),
},
resolve: {
modules: ['node_modules'].concat(process.env.NODE_PATH.split(path_1.default.delimiter).filter(Boolean)),
extensions: [
...(useTypescript
? paths_1.default.typescriptFileExtensions.map((ext) => '.' + ext)
: []),
...paths_1.default.moduleFileExtensions.map((ext) => `.${ext}`),
],
alias: Object.assign({}, (!isServer && !dev && profile
? {
'react-dom$': 'react-dom/profiling',
'scheduler/tracing': 'scheduler/tracing-profiling',
}
: null)),
},
module: {
strictExportPresence: true,
rules: [
{
test: /\.(tsx|ts|js|mjs|jsx)$/,
include: appSrcFiles,
exclude: (excludePath) => /node_modules/.test(excludePath),
loader: 'custom-babel-loader',
options: {
isServer,
dev,
hasReactRefresh: dev && !isServer,
hasJsxRuntime,
},
},
{
test: /\.(js|mjs)$/,
include: /node_modules/,
exclude: /@babel(?:\/|\\{1,2})runtime/,
use: [
!esbuildForDependencies && {
loader: require.resolve('babel-loader'),
options: {
babelrc: false,
configFile: false,
compact: false,
presets: [
[
require.resolve('@babel/preset-env'),
{
useBuiltIns: 'entry',
corejs: 3,
modules: false,
exclude: ['transform-typeof-symbol'],
},
],
],
plugins: [
[
require.resolve('@babel/plugin-transform-destructuring'),
{
loose: false,
selectiveLoose: [
'useState',
'useEffect',
'useContext',
'useReducer',
'useCallback',
'useMemo',
'useRef',
'useImperativeHandle',
'useLayoutEffect',
'useDebugValue',
],
},
],
require.resolve('@babel/plugin-transform-runtime'),
require.resolve('@babel/plugin-syntax-dynamic-import'),
],
// If an error happens in a package, it's possible to be
// because it was compiled. Thus, we don't want the browser
// debugger to show the original code. Instead, the code
// being evaluated would be much more helpful.
sourceMaps: false,
},
},
esbuildForDependencies && {
loader: require.resolve('esbuild-loader'),
options: {},
},
].filter(filterBoolean_1.filterBoolean),
},
...cssRules,
],
},
plugins: [
new mini_css_extract_plugin_1.default({
// Options similar to the same options in webpackOptions.output
// both options are optional
filename: `${constants_1.STATIC_CHUNKS_PATH}/${extractedCssFilename}.css`,
chunkFilename: `${constants_1.STATIC_CHUNKS_PATH}/${extractedCssFilename}.chunk.css`,
ignoreOrder: true,
}),
// Makes some environment variables available to the JS code, for example:
// if (process.env.NODE_ENV === 'development') { ... }. See `./env.ts`.
new webpack_1.default.DefinePlugin(env.stringified),
// Enable HMR for react components
dev && !isServer && new webpack_1.default.HotModuleReplacementPlugin(),
dev && !isServer && new react_refresh_webpack_plugin_1.default(),
routesManifestPluginInstance,
// Fix dynamic imports on server bundle
isServer && new SSRImportPlugin_1.default(),
// Watcher doesn't work well if you mistype casing in a path so we use
// a plugin that prints an error when you attempt to do this.
// See https://github.com/facebook/create-react-app/issues/240
dev && new case_sensitive_paths_webpack_plugin_1.default(),
!isServer &&
useTypescript &&
new fork_ts_checker_webpack_plugin_1.default({
typescript: {
typescriptPath,
mode: 'readonly',
diagnosticOptions: {
syntactic: true,
},
configFile: paths_1.default.appTsConfig,
configOverwrite: {
compilerOptions: {
isolatedModules: true,
noEmit: true,
incremental: true,
},
},
},
async: dev,
logger: {
infrastructure: 'silent',
issues: 'silent',
},
formatter: 'codeframe',
}),
].filter(filterBoolean_1.filterBoolean),
};
if (configFn) {
const userConfig = configFn(config, { isServer, dev });
if (typeof userConfig !== 'object' || 'then' in userConfig) {
log_1.warn('Webpack config function expected to return an object,' +
` but instead received "${typeof userConfig}".` +
(typeof userConfig === 'object' && 'then' in userConfig
? ' Did you accidentally return a Promise?'
: ''));
}
else {
config = userConfig;
}
}
return config;
};
exports.default = getBaseWebpackConfig;
;