UNPKG

casterly

Version:
536 lines (535 loc) 24.9 kB
"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 }); 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;