feat: solidifies bundler adapter pattern (#3044)

This commit is contained in:
Jarrod Flesch
2023-07-21 17:20:51 -04:00
committed by GitHub
parent 9f06253321
commit 641c765fb9
54 changed files with 2265 additions and 2030 deletions

View File

@@ -12,7 +12,7 @@ module.exports = {
'dist', 'dist',
], ],
moduleNameMapper: { moduleNameMapper: {
'\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': '<rootDir>/src/webpack/mocks/fileMock.js', '\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': '<rootDir>/src/bundlers/mocks/fileMock.js',
'\\.(css|scss)$': '<rootDir>/src/webpack/mocks/emptyModule.js', '\\.(css|scss)$': '<rootDir>/src/bundlers/mocks/emptyModule.js',
}, },
}; };

View File

@@ -11,7 +11,7 @@ module.exports = {
globalSetup: './test/jest.setup.ts', globalSetup: './test/jest.setup.ts',
testTimeout: 90000, testTimeout: 90000,
moduleNameMapper: { moduleNameMapper: {
'\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': '<rootDir>/src/webpack/mocks/fileMock.js', '\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': '<rootDir>/src/bundlers/mocks/fileMock.js',
'\\.(css|scss)$': '<rootDir>/src/webpack/mocks/emptyModule.js', '\\.(css|scss)$': '<rootDir>/src/bundlers/mocks/emptyModule.js',
}, },
}; };

View File

@@ -33,7 +33,7 @@
"scripts": { "scripts": {
"copyfiles": "copyfiles -u 1 \"src/**/*.{html,css,scss,ttf,woff,woff2,eot,svg,jpg,png}\" dist/", "copyfiles": "copyfiles -u 1 \"src/**/*.{html,css,scss,ttf,woff,woff2,eot,svg,jpg,png}\" dist/",
"build:tsc": "tsc", "build:tsc": "tsc",
"build:components": "webpack --config dist/webpack/components.config.js", "build:components": "webpack --config dist/bundlers/webpack/components.config.js",
"build": "yarn copyfiles && yarn build:tsc && yarn build:components", "build": "yarn copyfiles && yarn build:tsc && yarn build:components",
"build:watch": "nodemon --watch 'src/**' --ext 'ts,tsx' --exec \"yarn build:tsc\"", "build:watch": "nodemon --watch 'src/**' --ext 'ts,tsx' --exec \"yarn build:tsc\"",
"dev": "nodemon", "dev": "nodemon",

View File

@@ -4,7 +4,7 @@
<head> <head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no" /> <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no" />
<link rel="icon" href="data:," data-placeholder-favicon/> <link rel="icon" href="data:," data-placeholder-favicon />
</head> </head>
<body> <body>
@@ -12,4 +12,4 @@
<div id="portal"></div> <div id="portal"></div>
</body> </body>
</html> </html>

View File

@@ -3,7 +3,7 @@ import { extractTranslations } from '../translations/extractTranslations';
const labels = extractTranslations(['general:user', 'general:users']); const labels = extractTranslations(['general:user', 'general:users']);
const defaultUser: CollectionConfig = { export const defaultUserCollection: CollectionConfig = {
slug: 'users', slug: 'users',
labels: { labels: {
singular: labels['general:user'], singular: labels['general:user'],
@@ -17,5 +17,3 @@ const defaultUser: CollectionConfig = {
}, },
fields: [], fields: [],
}; };
export default defaultUser;

View File

@@ -1,33 +1,9 @@
/* eslint-disable @typescript-eslint/no-var-requires */
/* eslint-disable global-require */
import webpack from 'webpack';
import getWebpackProdConfig from '../webpack/getProdConfig';
import loadConfig from '../config/load'; import loadConfig from '../config/load';
export const build = async (): Promise<void> => { export const build = async (): Promise<void> => {
const config = await loadConfig(); // Will throw its own error if it fails const config = await loadConfig(); // Will throw its own error if it fails
try { await config.admin.bundler.build(config);
const webpackProdConfig = getWebpackProdConfig(config);
webpack(webpackProdConfig, (err, stats) => { // Stats Object
if (err || stats.hasErrors()) {
// Handle errors here
if (stats) {
console.error(stats.toString({
chunks: false,
colors: true,
}));
} else {
console.error(err.message);
}
}
});
} catch (err) {
console.error(err);
throw new Error('Error: there was an error building the webpack config.');
}
}; };
// when build.js is launched directly // when build.js is launched directly

View File

@@ -26,10 +26,10 @@ const swcOptions = {
}; };
if (tsConfig?.config?.compilerOptions?.paths) { if (tsConfig?.config?.compilerOptions?.paths) {
swcOptions.jsc.paths = tsConfig?.config?.compilerOptions?.paths; swcOptions.jsc.paths = tsConfig.config.compilerOptions.paths;
if (tsConfig?.config?.compilerOptions?.baseUrl) { if (tsConfig?.config?.compilerOptions?.baseUrl) {
swcOptions.jsc.baseUrl = tsConfig?.config?.compilerOptions?.baseUrl; swcOptions.jsc.baseUrl = tsConfig.config.compilerOptions.baseUrl;
} }
} }

View File

@@ -0,0 +1 @@
export default () => { };

8
src/bundlers/types.ts Normal file
View File

@@ -0,0 +1,8 @@
import type { PayloadHandler, SanitizedConfig } from 'payload/config';
import type { Payload } from '../payload';
export interface PayloadBundler {
dev: (payload: Payload) => Promise<PayloadHandler>, // this would be a typical Express middleware handler
build: (payloadConfig: SanitizedConfig) => Promise<void> // used in `payload build`
serve: (payload: Payload) => Promise<PayloadHandler> // serve built files in production
}

View File

@@ -0,0 +1,11 @@
/* eslint-disable @typescript-eslint/no-var-requires */
import { PayloadBundler } from '../types';
import { devAdmin } from './scripts/dev';
import { buildAdmin } from './scripts/build';
import { serveAdmin } from './scripts/serve';
export default (): PayloadBundler => ({
dev: async (payload) => devAdmin({ payload }),
build: async (payloadConfig) => buildAdmin({ payloadConfig }),
serve: async (payload) => serveAdmin({ payload }),
});

View File

@@ -5,13 +5,13 @@ import OptimizeCSSAssetsPlugin from 'css-minimizer-webpack-plugin';
export default { export default {
entry: { entry: {
main: [path.resolve(__dirname, '../admin/components/index.js')], main: [path.resolve(__dirname, '../../admin/components/index.js')],
}, },
externals: { externals: {
react: 'react', react: 'react',
}, },
output: { output: {
path: path.resolve(__dirname, '../../components'), path: path.resolve(__dirname, '../../../components'),
publicPath: '/', publicPath: '/',
filename: 'index.js', filename: 'index.js',
libraryTarget: 'commonjs2', libraryTarget: 'commonjs2',
@@ -82,8 +82,8 @@ export default {
], ],
resolve: { resolve: {
alias: { alias: {
'payload-scss-overrides': path.resolve(__dirname, '../admin/scss/overrides.scss'), 'payload-scss-overrides': path.resolve(__dirname, '../../admin/scss/overrides.scss'),
}, },
modules: ['node_modules', path.resolve(__dirname, '../../node_modules')], modules: ['node_modules', path.resolve(__dirname, '../../../node_modules')],
}, },
}; };

View File

@@ -1,19 +1,23 @@
import path from 'path'; import path from 'path';
import HtmlWebpackPlugin from 'html-webpack-plugin'; import HtmlWebpackPlugin from 'html-webpack-plugin';
import webpack, { Configuration } from 'webpack'; import webpack, { Configuration } from 'webpack';
import { SanitizedConfig } from '../config/types'; import type { SanitizedConfig } from '../../../config/types';
const mockModulePath = path.resolve(__dirname, './mocks/emptyModule.js'); const mockModulePath = path.resolve(__dirname, '../../mocks/emptyModule.js');
const mockDotENVPath = path.resolve(__dirname, './mocks/dotENV.js'); const mockDotENVPath = path.resolve(__dirname, '../../mocks/dotENV.js');
export default (config: SanitizedConfig): Configuration => ({ const nodeModulesPath = path.resolve(__dirname, '../../../../node_modules');
const adminFolderPath = path.resolve(__dirname, '../../../admin');
const bundlerPath = path.resolve(__dirname, '../bundler');
export const getBaseConfig = (payloadConfig: SanitizedConfig): Configuration => ({
entry: { entry: {
main: [ main: [
path.resolve(__dirname, '../admin'), adminFolderPath,
], ],
}, },
resolveLoader: { resolveLoader: {
modules: ['node_modules', path.join(__dirname, '../../node_modules')], modules: ['node_modules', path.join(__dirname, nodeModulesPath)],
}, },
module: { module: {
rules: [ rules: [
@@ -51,12 +55,13 @@ export default (config: SanitizedConfig): Configuration => ({
https: false, https: false,
http: false, http: false,
}, },
modules: ['node_modules', path.resolve(__dirname, '../../node_modules')], modules: ['node_modules', path.resolve(__dirname, nodeModulesPath)],
alias: { alias: {
'payload-config': config.paths.rawConfig, 'payload-config': payloadConfig.paths.rawConfig,
payload$: mockModulePath, payload$: mockModulePath,
'payload-user-css': config.admin.css, 'payload-user-css': payloadConfig.admin.css,
dotenv: mockDotENVPath, dotenv: mockDotENVPath,
[bundlerPath]: mockModulePath,
}, },
extensions: ['.ts', '.tsx', '.js', '.json'], extensions: ['.ts', '.tsx', '.js', '.json'],
}, },
@@ -80,7 +85,7 @@ export default (config: SanitizedConfig): Configuration => ({
), ),
), ),
new HtmlWebpackPlugin({ new HtmlWebpackPlugin({
template: config.admin.indexHTML, template: payloadConfig.admin.indexHTML,
filename: path.normalize('./index.html'), filename: path.normalize('./index.html'),
}), }),
new webpack.HotModuleReplacementPlugin(), new webpack.HotModuleReplacementPlugin(),

View File

@@ -1,12 +1,12 @@
import webpack, { Configuration } from 'webpack'; import webpack, { Configuration } from 'webpack';
import md5 from 'md5'; import md5 from 'md5';
import { SanitizedConfig } from '../config/types'; import { getBaseConfig } from './base';
import getBaseConfig from './getBaseConfig'; import { SanitizedConfig } from '../../../config/types';
export default (payloadConfig: SanitizedConfig): Configuration => { export const getDevConfig = (payloadConfig: SanitizedConfig): Configuration => {
const baseConfig = getBaseConfig(payloadConfig) as any; const baseConfig = getBaseConfig(payloadConfig) as any;
let config: Configuration = { let webpackConfig: Configuration = {
...baseConfig, ...baseConfig,
cache: { cache: {
type: 'filesystem', type: 'filesystem',
@@ -37,7 +37,7 @@ export default (payloadConfig: SanitizedConfig): Configuration => {
], ],
}; };
config.module.rules.push({ webpackConfig.module.rules.push({
test: /\.(scss|css)$/, test: /\.(scss|css)$/,
sideEffects: true, sideEffects: true,
use: [ use: [
@@ -61,8 +61,8 @@ export default (payloadConfig: SanitizedConfig): Configuration => {
}); });
if (payloadConfig.admin.webpack && typeof payloadConfig.admin.webpack === 'function') { if (payloadConfig.admin.webpack && typeof payloadConfig.admin.webpack === 'function') {
config = payloadConfig.admin.webpack(config); webpackConfig = payloadConfig.admin.webpack(webpackConfig);
} }
return config; return webpackConfig;
}; };

View File

@@ -1,14 +1,14 @@
import { Configuration, WebpackPluginInstance } from 'webpack';
import MiniCSSExtractPlugin from 'mini-css-extract-plugin'; import MiniCSSExtractPlugin from 'mini-css-extract-plugin';
import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer'; import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer';
import { Configuration, WebpackPluginInstance } from 'webpack';
import { SwcMinifyWebpackPlugin } from 'swc-minify-webpack-plugin'; import { SwcMinifyWebpackPlugin } from 'swc-minify-webpack-plugin';
import { SanitizedConfig } from '../config/types'; import { getBaseConfig } from './base';
import getBaseConfig from './getBaseConfig'; import { SanitizedConfig } from '../../../config/types';
export default (payloadConfig: SanitizedConfig): Configuration => { export const getProdConfig = (payloadConfig: SanitizedConfig): Configuration => {
const baseConfig = getBaseConfig(payloadConfig) as any; const baseConfig = getBaseConfig(payloadConfig) as any;
let config: Configuration = { let webpackConfig: Configuration = {
...baseConfig, ...baseConfig,
output: { output: {
publicPath: `${payloadConfig.routes.admin}/`, publicPath: `${payloadConfig.routes.admin}/`,
@@ -40,7 +40,7 @@ export default (payloadConfig: SanitizedConfig): Configuration => {
], ],
}; };
config.module.rules.push({ webpackConfig.module.rules.push({
test: /\.(scss|css)$/, test: /\.(scss|css)$/,
sideEffects: true, sideEffects: true,
use: [ use: [
@@ -64,12 +64,12 @@ export default (payloadConfig: SanitizedConfig): Configuration => {
}); });
if (process.env.PAYLOAD_ANALYZE_BUNDLE) { if (process.env.PAYLOAD_ANALYZE_BUNDLE) {
config.plugins.push(new BundleAnalyzerPlugin() as unknown as WebpackPluginInstance); webpackConfig.plugins.push(new BundleAnalyzerPlugin() as unknown as WebpackPluginInstance);
} }
if (payloadConfig.admin.webpack && typeof payloadConfig.admin.webpack === 'function') { if (payloadConfig.admin.webpack && typeof payloadConfig.admin.webpack === 'function') {
config = payloadConfig.admin.webpack(config); webpackConfig = payloadConfig.admin.webpack(webpackConfig);
} }
return config; return webpackConfig;
}; };

View File

@@ -0,0 +1,27 @@
import webpack from 'webpack';
import { getProdConfig } from '../configs/prod';
import { SanitizedConfig } from '../../../config/types';
type BuildAdminType = (options: { payloadConfig: SanitizedConfig }) => Promise<void>;
export const buildAdmin: BuildAdminType = async ({ payloadConfig }) => {
try {
const webpackConfig = getProdConfig(payloadConfig);
webpack(webpackConfig, (err, stats) => {
if (err || stats.hasErrors()) {
// Handle errors here
if (stats) {
console.error(stats.toString({
chunks: false,
colors: true,
}));
} else {
console.error(err.message);
}
}
});
} catch (err) {
console.error(err);
throw new Error('Error: there was an error building the webpack prod config.');
}
};

View File

@@ -0,0 +1,31 @@
import webpack from 'webpack';
import express from 'express';
import webpackDevMiddleware from 'webpack-dev-middleware';
import webpackHotMiddleware from 'webpack-hot-middleware';
import history from 'connect-history-api-fallback';
import type { PayloadHandler } from '../../../config/types';
import { Payload } from '../../../payload';
import { getDevConfig } from '../configs/dev';
const router = express.Router();
type DevAdminType = (options: { payload: Payload }) => Promise<PayloadHandler>;
export const devAdmin: DevAdminType = async ({ payload }) => {
payload.express.use(payload.config.routes.admin, history());
try {
const webpackConfig = getDevConfig(payload.config);
const compiler = webpack(webpackConfig);
router.use(webpackDevMiddleware(compiler, {
publicPath: webpackConfig.output.publicPath as string,
}));
router.use(webpackHotMiddleware(compiler));
} catch (err) {
console.error(err);
throw new Error('Error: there was an error creating the webpack dev server.');
}
return router;
};

View File

@@ -0,0 +1,27 @@
import express from 'express';
import compression from 'compression';
import history from 'connect-history-api-fallback';
import type { PayloadHandler } from '../../../config/types';
import { Payload } from '../../../payload';
const router = express.Router();
type ServeAdminType = (options: { payload: Payload }) => Promise<PayloadHandler>;
export const serveAdmin: ServeAdminType = async ({ payload }) => {
router.use(payload.config.routes.admin, history());
router.get('*', (req, res, next) => {
if (req.path.substr(-1) === '/' && req.path.length > 1) {
const query = req.url.slice(req.path.length);
res.redirect(301, req.path.slice(0, -1) + query);
} else {
next();
}
});
router.use(compression(payload.config.express.compression));
router.use(express.static(payload.config.admin.buildPath, { redirect: false }));
return router;
};

View File

@@ -1,7 +1,7 @@
/* eslint-disable no-use-before-define */ /* eslint-disable no-use-before-define */
/* eslint-disable no-nested-ternary */ /* eslint-disable no-nested-ternary */
import { Config, SanitizedConfig } from './types'; import { Config, SanitizedConfig } from './types';
import sanitize from './sanitize'; import { sanitizeConfig } from './sanitize';
/** /**
* @description Builds and validates Payload configuration * @description Builds and validates Payload configuration
@@ -18,10 +18,10 @@ export async function buildConfig(config: Config): Promise<SanitizedConfig> {
Promise.resolve(config), Promise.resolve(config),
); );
const sanitizedConfig = sanitize(configAfterPlugins); const sanitizedConfig = sanitizeConfig(configAfterPlugins);
return sanitizedConfig; return sanitizedConfig;
} }
return sanitize(config); return sanitizeConfig(config);
} }

View File

@@ -1,30 +1,47 @@
import merge from 'deepmerge'; import merge from 'deepmerge';
import { isPlainObject } from 'is-plain-object'; import { isPlainObject } from 'is-plain-object';
import { Config, SanitizedConfig } from './types'; import { Config, SanitizedConfig } from './types';
import defaultUser from '../auth/defaultUser'; import { defaultUserCollection } from '../auth/defaultUser';
import sanitizeCollection from '../collections/config/sanitize'; import sanitizeCollection from '../collections/config/sanitize';
import { InvalidConfiguration } from '../errors'; import { InvalidConfiguration } from '../errors';
import sanitizeGlobals from '../globals/config/sanitize'; import sanitizeGlobals from '../globals/config/sanitize';
import checkDuplicateCollections from '../utilities/checkDuplicateCollections'; import checkDuplicateCollections from '../utilities/checkDuplicateCollections';
import { defaults } from './defaults'; import { defaults } from './defaults';
import getDefaultBundler from '../bundlers/webpack/bundler';
const sanitizeConfig = (config: Config): SanitizedConfig => { const sanitizeAdmin = (config: SanitizedConfig): SanitizedConfig['admin'] => {
const sanitizedConfig = merge(defaults, config, { const adminConfig = config.admin;
// add default user collection if none provided
if (!adminConfig?.user) {
const firstCollectionWithAuth = config.collections.find(({ auth }) => Boolean(auth));
if (firstCollectionWithAuth) {
adminConfig.user = firstCollectionWithAuth.slug;
} else {
adminConfig.user = 'users';
const sanitizedDefaultUser = sanitizeCollection(config, defaultUserCollection);
config.collections.push(sanitizedDefaultUser);
}
}
if (!config.collections.find(({ slug }) => slug === adminConfig.user)) {
throw new InvalidConfiguration(`${config.admin.user} is not a valid admin user collection`);
}
// add default bundler if none provided
if (!adminConfig.bundler) {
adminConfig.bundler = getDefaultBundler();
}
return adminConfig;
};
export const sanitizeConfig = (config: Config): SanitizedConfig => {
const sanitizedConfig: Config = merge(defaults, config, {
isMergeableObject: isPlainObject, isMergeableObject: isPlainObject,
}) as Config; }) as Config;
if (!sanitizedConfig.admin.user) { sanitizedConfig.admin = sanitizeAdmin(sanitizedConfig as SanitizedConfig);
const firstCollectionWithAuth = sanitizedConfig.collections.find((c) => c.auth);
if (firstCollectionWithAuth) {
sanitizedConfig.admin.user = firstCollectionWithAuth.slug;
} else {
sanitizedConfig.admin.user = 'users';
const sanitizedDefaultUser = sanitizeCollection(sanitizedConfig, defaultUser);
sanitizedConfig.collections.push(sanitizedDefaultUser);
}
} else if (!sanitizedConfig.collections.find((c) => c.slug === sanitizedConfig.admin.user)) {
throw new InvalidConfiguration(`${sanitizedConfig.admin.user} is not a valid admin user collection`);
}
sanitizedConfig.collections = sanitizedConfig.collections.map((collection) => sanitizeCollection(sanitizedConfig, collection)); sanitizedConfig.collections = sanitizedConfig.collections.map((collection) => sanitizeCollection(sanitizedConfig, collection));
checkDuplicateCollections(sanitizedConfig.collections); checkDuplicateCollections(sanitizedConfig.collections);
@@ -43,5 +60,3 @@ const sanitizeConfig = (config: Config): SanitizedConfig => {
return sanitizedConfig as SanitizedConfig; return sanitizedConfig as SanitizedConfig;
}; };
export default sanitizeConfig;

View File

@@ -107,6 +107,11 @@ export default joi.object({
}), }),
}), }),
webpack: joi.func(), webpack: joi.func(),
bundler: {
dev: joi.func(),
build: joi.func(),
serve: joi.func(),
},
}), }),
email: joi.object(), email: joi.object(),
i18n: joi.object(), i18n: joi.object(),

View File

@@ -2,7 +2,7 @@ import { Express, NextFunction, Response } from 'express';
import { DeepRequired } from 'ts-essentials'; import { DeepRequired } from 'ts-essentials';
import { Transporter } from 'nodemailer'; import { Transporter } from 'nodemailer';
import { Options as ExpressFileUploadOptions } from 'express-fileupload'; import { Options as ExpressFileUploadOptions } from 'express-fileupload';
import { Configuration } from 'webpack'; import type { Configuration } from 'webpack';
import SMTPConnection from 'nodemailer/lib/smtp-connection'; import SMTPConnection from 'nodemailer/lib/smtp-connection';
import GraphQL from 'graphql'; import GraphQL from 'graphql';
import { ConnectOptions } from 'mongoose'; import { ConnectOptions } from 'mongoose';
@@ -19,6 +19,7 @@ import { GlobalConfig, SanitizedGlobalConfig } from '../globals/config/types';
import { PayloadRequest } from '../express/types'; import { PayloadRequest } from '../express/types';
import { Where } from '../types'; import { Where } from '../types';
import { User } from '../auth/types'; import { User } from '../auth/types';
import type { PayloadBundler } from '../bundlers/types';
type Email = { type Email = {
fromName: string; fromName: string;
@@ -266,7 +267,11 @@ export type Config = {
*/ */
favicon?: string; favicon?: string;
}; };
/** Specify an absolute path for where to store the built Admin panel bundle used in production. */ /**
* Specify an absolute path for where to store the built Admin panel bundle used in production.
*
* @default "/build"
* */
buildPath?: string buildPath?: string
/** If set to true, the entire Admin panel will be disabled. */ /** If set to true, the entire Admin panel will be disabled. */
disable?: boolean; disable?: boolean;
@@ -353,6 +358,8 @@ export type Config = {
}; };
/** Customize the Webpack config that's used to generate the Admin panel. */ /** Customize the Webpack config that's used to generate the Admin panel. */
webpack?: (config: Configuration) => Configuration; webpack?: (config: Configuration) => Configuration;
/** Customize the bundler used to run your admin panel. */
bundler?: PayloadBundler;
}; };
/** /**
* Manage the datamodel of your application * Manage the datamodel of your application
@@ -407,13 +414,13 @@ export type Config = {
cors?: string[] | '*'; cors?: string[] | '*';
/** Control the routing structure that Payload binds itself to. */ /** Control the routing structure that Payload binds itself to. */
routes?: { routes?: {
/** Defaults to /api */ /** @default "/api" */
api?: string; api?: string;
/** Defaults to /admin */ /** @default "/admin" */
admin?: string; admin?: string;
/** Defaults to /graphql */ /** @default "/graphql" */
graphQL?: string; graphQL?: string;
/** Defaults to /playground */ /** @default "/playground" */
graphQLPlayground?: string; graphQLPlayground?: string;
}; };
/** Control how typescript interfaces are generated from your collections. */ /** Control how typescript interfaces are generated from your collections. */

View File

@@ -1,32 +1,11 @@
import express from 'express';
import compression from 'compression';
import history from 'connect-history-api-fallback';
import initWebpack from '../webpack/init';
import { Payload } from '../payload'; import { Payload } from '../payload';
const router = express.Router(); async function initAdmin(ctx: Payload): Promise<void> {
function initAdmin(ctx: Payload): void {
if (!ctx.config.admin.disable) { if (!ctx.config.admin.disable) {
router.use(history());
if (process.env.NODE_ENV === 'production') { if (process.env.NODE_ENV === 'production') {
router.get('*', (req, res, next) => { ctx.express.use(await ctx.config.admin.bundler.serve(ctx));
if (req.path.substr(-1) === '/' && req.path.length > 1) {
const query = req.url.slice(req.path.length);
res.redirect(301, req.path.slice(0, -1) + query);
} else {
next();
}
});
router.use(compression(ctx.config.express.compression));
router.use(express.static(ctx.config.admin.buildPath, { redirect: false }));
ctx.express.use(ctx.config.routes.admin, router);
} else { } else {
ctx.express.use(ctx.config.routes.admin, history()); ctx.express.use(await ctx.config.admin.bundler.dev(ctx));
ctx.express.use(initWebpack(ctx.config));
} }
} }
} }

View File

@@ -48,7 +48,7 @@ export const initHTTP = async (options: InitOptions): Promise<Payload> => {
payload.express.set('trust proxy', 1); payload.express.set('trust proxy', 1);
} }
initAdmin(payload); await initAdmin(payload);
initPreferences(payload); initPreferences(payload);
payload.router.get('/access', access); payload.router.get('/access', access);

View File

@@ -1,4 +1,4 @@
import sanitizeConfig from '../config/sanitize'; import { sanitizeConfig } from '../config/sanitize';
import { Config } from '../config/types'; import { Config } from '../config/types';
import { configToJSONSchema } from './configToJSONSchema'; import { configToJSONSchema } from './configToJSONSchema';

View File

@@ -1,23 +0,0 @@
import webpack, { Compiler } from 'webpack';
import express, { Router } from 'express';
import webpackDevMiddleware from 'webpack-dev-middleware';
import webpackHotMiddleware from 'webpack-hot-middleware';
import getWebpackDevConfig from './getDevConfig';
import { SanitizedConfig } from '../config/types';
const router = express.Router();
function initWebpack(config: SanitizedConfig): Router {
const webpackDevConfig = getWebpackDevConfig(config);
const compiler = webpack(webpackDevConfig);
router.use(webpackDevMiddleware(compiler, {
publicPath: webpackDevConfig.output.publicPath as string,
}));
router.use(webpackHotMiddleware(compiler as any));
return router;
}
export default initWebpack;

View File

@@ -1 +0,0 @@
export default () => {};

View File

@@ -1,10 +1,10 @@
import { buildConfig } from '../buildConfig'; import { buildConfigWithDefaults } from '../buildConfigWithDefaults';
import { PostsCollection, postsSlug } from './collections/Posts'; import { PostsCollection, postsSlug } from './collections/Posts';
import { MenuGlobal } from './globals/Menu'; import { MenuGlobal } from './globals/Menu';
import { devUser } from '../credentials'; import { devUser } from '../credentials';
import { MediaCollection } from './collections/Media'; import { MediaCollection } from './collections/Media';
export default buildConfig({ export default buildConfigWithDefaults({
// ...extend config here // ...extend config here
collections: [ collections: [
PostsCollection, PostsCollection,

View File

@@ -1,5 +1,5 @@
import { devUser } from '../credentials'; import { devUser } from '../credentials';
import { buildConfig } from '../buildConfig'; import { buildConfigWithDefaults } from '../buildConfigWithDefaults';
import { FieldAccess } from '../../src/fields/config/types'; import { FieldAccess } from '../../src/fields/config/types';
import { firstArrayText, secondArrayText } from './shared'; import { firstArrayText, secondArrayText } from './shared';
@@ -36,7 +36,7 @@ const UseRequestHeadersAccess: FieldAccess = ({ req: { headers } }) => {
return !!headers && headers.authorization === requestHeaders.authorization; return !!headers && headers.authorization === requestHeaders.authorization;
}; };
export default buildConfig({ export default buildConfigWithDefaults({
admin: { admin: {
user: 'users', user: 'users',
}, },

View File

@@ -1,7 +1,7 @@
import path from 'path'; import path from 'path';
import { mapAsync } from '../../src/utilities/mapAsync'; import { mapAsync } from '../../src/utilities/mapAsync';
import { devUser } from '../credentials'; import { devUser } from '../credentials';
import { buildConfig } from '../buildConfig'; import { buildConfigWithDefaults } from '../buildConfigWithDefaults';
import AfterDashboard from './components/AfterDashboard'; import AfterDashboard from './components/AfterDashboard';
import CustomMinimalRoute from './components/views/CustomMinimal'; import CustomMinimalRoute from './components/views/CustomMinimal';
import CustomDefaultRoute from './components/views/CustomDefault'; import CustomDefaultRoute from './components/views/CustomDefault';
@@ -20,7 +20,7 @@ export interface Post {
updatedAt: Date; updatedAt: Date;
} }
export default buildConfig({ export default buildConfigWithDefaults({
admin: { admin: {
css: path.resolve(__dirname, 'styles.scss'), css: path.resolve(__dirname, 'styles.scss'),
components: { components: {

View File

@@ -1,6 +1,6 @@
import { buildConfig } from '../buildConfig'; import { buildConfigWithDefaults } from '../buildConfigWithDefaults';
export default buildConfig({ export default buildConfigWithDefaults({
collections: [ collections: [
{ {
slug: 'arrays', slug: 'arrays',

View File

@@ -1,12 +1,12 @@
import { v4 as uuid } from 'uuid'; import { v4 as uuid } from 'uuid';
import { mapAsync } from '../../src/utilities/mapAsync'; import { mapAsync } from '../../src/utilities/mapAsync';
import { buildConfig } from '../buildConfig'; import { buildConfigWithDefaults } from '../buildConfigWithDefaults';
import { devUser } from '../credentials'; import { devUser } from '../credentials';
import { AuthDebug } from './AuthDebug'; import { AuthDebug } from './AuthDebug';
export const slug = 'users'; export const slug = 'users';
export default buildConfig({ export default buildConfigWithDefaults({
admin: { admin: {
user: 'users', user: 'users',
autoLogin: false, autoLogin: false,

View File

@@ -1,7 +1,7 @@
import { Request } from 'express'; import { Request } from 'express';
import { Strategy } from 'passport-strategy'; import { Strategy } from 'passport-strategy';
import { Payload } from '../../../src/payload'; import { Payload } from '../../../src/payload';
import { buildConfig } from '../../buildConfig'; import { buildConfigWithDefaults } from '../../buildConfigWithDefaults';
export const slug = 'users'; export const slug = 'users';
export const strategyName = 'test-local'; export const strategyName = 'test-local';
@@ -41,7 +41,7 @@ export class CustomStrategy extends Strategy {
} }
} }
export default buildConfig({ export default buildConfigWithDefaults({
admin: { admin: {
user: 'users', user: 'users',
}, },

View File

@@ -1,40 +1,41 @@
import { Config, SanitizedConfig } from '../src/config/types'; import { Config, SanitizedConfig } from '../src/config/types';
import { buildConfig as buildPayloadConfig } from '../src/config/build'; import { buildConfig as buildPayloadConfig } from '../src/config/build';
export function buildConfig(config?: Partial<Config>): Promise<SanitizedConfig> { export function buildConfigWithDefaults(testConfig?: Partial<Config>): Promise<SanitizedConfig> {
const [name] = process.argv.slice(2); const [name] = process.argv.slice(2);
const baseConfig: Config = { const config: Config = {
telemetry: false, telemetry: false,
rateLimit: { rateLimit: {
window: 15 * 60 * 100, // 15min default, window: 15 * 60 * 100, // 15min default,
max: 9999999999, max: 9999999999,
}, },
...config, ...testConfig,
}; };
baseConfig.admin = {
config.admin = {
autoLogin: process.env.PAYLOAD_PUBLIC_DISABLE_AUTO_LOGIN === 'true' ? false : { autoLogin: process.env.PAYLOAD_PUBLIC_DISABLE_AUTO_LOGIN === 'true' ? false : {
email: 'dev@payloadcms.com', email: 'dev@payloadcms.com',
password: 'test', password: 'test',
}, },
...(baseConfig.admin || {}), ...(config.admin || {}),
webpack: (webpackConfig) => { webpack: (webpackConfig) => {
const existingConfig = typeof config?.admin?.webpack === 'function' const existingConfig = typeof testConfig?.admin?.webpack === 'function'
? config.admin.webpack(webpackConfig) ? testConfig.admin.webpack(webpackConfig)
: webpackConfig; : webpackConfig;
return { return {
...existingConfig, ...existingConfig,
name, name,
cache: process.env.NODE_ENV === 'test' ? { cache: process.env.NODE_ENV === 'test'
type: 'memory', ? { type: 'memory' }
} : existingConfig.cache, : existingConfig.cache,
}; };
}, },
}; };
if (process.env.PAYLOAD_DISABLE_ADMIN === 'true') { if (process.env.PAYLOAD_DISABLE_ADMIN === 'true') {
if (typeof baseConfig.admin !== 'object') baseConfig.admin = {}; if (typeof config.admin !== 'object') config.admin = {};
baseConfig.admin.disable = true; config.admin.disable = true;
} }
return buildPayloadConfig(baseConfig); return buildPayloadConfig(config);
} }

View File

@@ -1,7 +1,7 @@
import path from 'path'; import path from 'path';
import type { CollectionConfig } from '../../src/collections/config/types'; import type { CollectionConfig } from '../../src/collections/config/types';
import { devUser } from '../credentials'; import { devUser } from '../credentials';
import { buildConfig } from '../buildConfig'; import { buildConfigWithDefaults } from '../buildConfigWithDefaults';
export interface Relation { export interface Relation {
id: string; id: string;
@@ -30,7 +30,7 @@ const collectionWithName = (collectionSlug: string): CollectionConfig => {
export const slug = 'posts'; export const slug = 'posts';
export const relationSlug = 'relation'; export const relationSlug = 'relation';
export default buildConfig({ export default buildConfigWithDefaults({
graphQL: { graphQL: {
schemaOutputFile: path.resolve(__dirname, 'schema.graphql'), schemaOutputFile: path.resolve(__dirname, 'schema.graphql'),
}, },

View File

@@ -1,6 +1,6 @@
import type { CollectionConfig } from '../../src/collections/config/types'; import type { CollectionConfig } from '../../src/collections/config/types';
import { devUser } from '../credentials'; import { devUser } from '../credentials';
import { buildConfig } from '../buildConfig'; import { buildConfigWithDefaults } from '../buildConfigWithDefaults';
export interface Relation { export interface Relation {
id: string; id: string;
@@ -34,7 +34,7 @@ export const customIdSlug = 'custom-id';
export const customIdNumberSlug = 'custom-id-number'; export const customIdNumberSlug = 'custom-id-number';
export const errorOnHookSlug = 'error-on-hooks'; export const errorOnHookSlug = 'error-on-hooks';
export default buildConfig({ export default buildConfigWithDefaults({
endpoints: [ endpoints: [
{ {
path: '/send-test-email', path: '/send-test-email',

View File

@@ -1,4 +1,4 @@
import { buildConfig } from '../buildConfig'; import { buildConfigWithDefaults } from '../buildConfigWithDefaults';
import { openAccess } from '../helpers/configHelpers'; import { openAccess } from '../helpers/configHelpers';
import { Config } from '../../src/config/types'; import { Config } from '../../src/config/types';
@@ -62,4 +62,4 @@ const config: Config = {
custom: { name: 'Customer portal' }, custom: { name: 'Customer portal' },
}; };
export default buildConfig(config); export default buildConfigWithDefaults(config);

View File

@@ -1,7 +1,7 @@
import { buildConfig } from '../buildConfig'; import { buildConfigWithDefaults } from '../buildConfigWithDefaults';
import { devUser } from '../credentials'; import { devUser } from '../credentials';
export default buildConfig({ export default buildConfigWithDefaults({
collections: [ collections: [
{ {
slug: 'posts', slug: 'posts',

View File

@@ -1,6 +1,6 @@
import express, { Response } from 'express'; import express, { Response } from 'express';
import { devUser } from '../credentials'; import { devUser } from '../credentials';
import { buildConfig } from '../buildConfig'; import { buildConfigWithDefaults } from '../buildConfigWithDefaults';
import { openAccess } from '../helpers/configHelpers'; import { openAccess } from '../helpers/configHelpers';
import { PayloadRequest } from '../../src/express/types'; import { PayloadRequest } from '../../src/express/types';
import { Config } from '../../src/config/types'; import { Config } from '../../src/config/types';
@@ -124,4 +124,4 @@ const MyConfig: Config = {
}, },
}; };
export default buildConfig(MyConfig); export default buildConfigWithDefaults(MyConfig);

View File

@@ -1,9 +1,9 @@
import { buildConfig } from '../buildConfig'; import { buildConfigWithDefaults } from '../buildConfigWithDefaults';
import { ErrorFieldsCollection } from './collections/ErrorFields'; import { ErrorFieldsCollection } from './collections/ErrorFields';
import { devUser } from '../credentials'; import { devUser } from '../credentials';
import Uploads from './collections/Upload'; import Uploads from './collections/Upload';
export default buildConfig({ export default buildConfigWithDefaults({
collections: [ collections: [
ErrorFieldsCollection, ErrorFieldsCollection,
Uploads, Uploads,

View File

@@ -1,8 +1,8 @@
/* eslint-disable @typescript-eslint/ban-ts-comment */ /* eslint-disable @typescript-eslint/ban-ts-comment */
import { buildConfig } from '../buildConfig'; import { buildConfigWithDefaults } from '../buildConfigWithDefaults';
import { devUser } from '../credentials'; import { devUser } from '../credentials';
export default buildConfig({ export default buildConfigWithDefaults({
collections: [ collections: [
{ {
slug: 'blocks-collection', slug: 'blocks-collection',

View File

@@ -1,5 +1,5 @@
import type { CollectionConfig } from '../../src/collections/config/types'; import type { CollectionConfig } from '../../src/collections/config/types';
import { buildConfig } from '../buildConfig'; import { buildConfigWithDefaults } from '../buildConfigWithDefaults';
import { devUser } from '../credentials'; import { devUser } from '../credentials';
import { mapAsync } from '../../src/utilities/mapAsync'; import { mapAsync } from '../../src/utilities/mapAsync';
import { FilterOptionsProps } from '../../src/fields/config/types'; import { FilterOptionsProps } from '../../src/fields/config/types';
@@ -33,7 +33,7 @@ const baseRelationshipFields: CollectionConfig['fields'] = [
}, },
]; ];
export default buildConfig({ export default buildConfigWithDefaults({
collections: [ collections: [
{ {
slug, slug,

View File

@@ -1,7 +1,7 @@
/* eslint-disable @typescript-eslint/ban-ts-comment */ /* eslint-disable @typescript-eslint/ban-ts-comment */
import path from 'path'; import path from 'path';
import fs from 'fs'; import fs from 'fs';
import { buildConfig } from '../buildConfig'; import { buildConfigWithDefaults } from '../buildConfigWithDefaults';
import { devUser } from '../credentials'; import { devUser } from '../credentials';
import ArrayFields, { arrayDoc } from './collections/Array'; import ArrayFields, { arrayDoc } from './collections/Array';
import BlockFields, { blocksDoc } from './collections/Blocks'; import BlockFields, { blocksDoc } from './collections/Blocks';
@@ -26,7 +26,7 @@ import Uploads2 from './collections/Upload2';
import Uploads3 from './collections/Uploads3'; import Uploads3 from './collections/Uploads3';
import RowFields from './collections/Row'; import RowFields from './collections/Row';
export default buildConfig({ export default buildConfigWithDefaults({
admin: { admin: {
webpack: (config) => ({ webpack: (config) => ({
...config, ...config,

View File

@@ -1,5 +1,5 @@
import { devUser } from '../credentials'; import { devUser } from '../credentials';
import { buildConfig } from '../buildConfig'; import { buildConfigWithDefaults } from '../buildConfigWithDefaults';
export const slug = 'global'; export const slug = 'global';
export const arraySlug = 'array'; export const arraySlug = 'array';
@@ -16,7 +16,7 @@ const access = {
update: () => true, update: () => true,
}; };
export default buildConfig({ export default buildConfigWithDefaults({
localization: { localization: {
locales: [englishLocale, spanishLocale], locales: [englishLocale, spanishLocale],
defaultLocale: englishLocale, defaultLocale: englishLocale,

View File

@@ -1,7 +1,7 @@
import path from 'path'; import path from 'path';
import { buildConfig } from '../buildConfig'; import { buildConfigWithDefaults } from '../buildConfigWithDefaults';
export default buildConfig({ export default buildConfigWithDefaults({
graphQL: { graphQL: {
schemaOutputFile: path.resolve(__dirname, 'schema.graphql'), schemaOutputFile: path.resolve(__dirname, 'schema.graphql'),
}, },

View File

@@ -1,4 +1,4 @@
import { buildConfig } from '../buildConfig'; import { buildConfigWithDefaults } from '../buildConfigWithDefaults';
import TransformHooks from './collections/Transform'; import TransformHooks from './collections/Transform';
import Hooks, { hooksSlug } from './collections/Hook'; import Hooks, { hooksSlug } from './collections/Hook';
import NestedAfterReadHooks from './collections/NestedAfterReadHooks'; import NestedAfterReadHooks from './collections/NestedAfterReadHooks';
@@ -6,7 +6,7 @@ import ChainingHooks from './collections/ChainingHooks';
import Relations from './collections/Relations'; import Relations from './collections/Relations';
import Users, { seedHooksUsers } from './collections/Users'; import Users, { seedHooksUsers } from './collections/Users';
export default buildConfig({ export default buildConfigWithDefaults({
collections: [ collections: [
TransformHooks, TransformHooks,
Hooks, Hooks,

View File

@@ -1,4 +1,4 @@
import { buildConfig } from '../buildConfig'; import { buildConfigWithDefaults } from '../buildConfigWithDefaults';
import { devUser } from '../credentials'; import { devUser } from '../credentials';
import { ArrayCollection } from './collections/Array'; import { ArrayCollection } from './collections/Array';
import { LocalizedPost, RelationshipLocalized } from './payload-types'; import { LocalizedPost, RelationshipLocalized } from './payload-types';
@@ -32,7 +32,7 @@ const openAccess = {
update: () => true, update: () => true,
}; };
export default buildConfig({ export default buildConfigWithDefaults({
localization: { localization: {
locales: [defaultLocale, spanishLocale], locales: [defaultLocale, spanishLocale],
defaultLocale, defaultLocale,

View File

@@ -1,9 +1,9 @@
import { buildConfig } from '../buildConfig'; import { buildConfigWithDefaults } from '../buildConfigWithDefaults';
import { devUser } from '../credentials'; import { devUser } from '../credentials';
export const pagesSlug = 'pages'; export const pagesSlug = 'pages';
export default buildConfig({ export default buildConfigWithDefaults({
collections: [ collections: [
{ {
slug: 'users', slug: 'users',

View File

@@ -1,10 +1,10 @@
import { buildConfig } from '../buildConfig'; import { buildConfigWithDefaults } from '../buildConfigWithDefaults';
import { devUser } from '../credentials'; import { devUser } from '../credentials';
import GlobalViewWithRefresh from './GlobalViewWithRefresh'; import GlobalViewWithRefresh from './GlobalViewWithRefresh';
export const pagesSlug = 'pages'; export const pagesSlug = 'pages';
export default buildConfig({ export default buildConfigWithDefaults({
globals: [ globals: [
{ {
slug: 'settings', slug: 'settings',

View File

@@ -1,6 +1,6 @@
import type { CollectionConfig } from '../../src/collections/config/types'; import type { CollectionConfig } from '../../src/collections/config/types';
import { devUser } from '../credentials'; import { devUser } from '../credentials';
import { buildConfig } from '../buildConfig'; import { buildConfigWithDefaults } from '../buildConfigWithDefaults';
const openAccess = { const openAccess = {
create: () => true, create: () => true,
@@ -41,7 +41,7 @@ export const defaultAccessRelSlug = 'strict-access';
export const chainedRelSlug = 'chained-relation'; export const chainedRelSlug = 'chained-relation';
export const customIdSlug = 'custom-id-relation'; export const customIdSlug = 'custom-id-relation';
export const customIdNumberSlug = 'custom-id-number-relation'; export const customIdNumberSlug = 'custom-id-number-relation';
export default buildConfig({ export default buildConfigWithDefaults({
collections: [ collections: [
{ {
slug, slug,

View File

@@ -1,5 +1,5 @@
import path from 'path'; import path from 'path';
import { buildConfig } from '../buildConfig'; import { buildConfigWithDefaults } from '../buildConfigWithDefaults';
import { devUser } from '../credentials'; import { devUser } from '../credentials';
import getFileByPath from '../../src/uploads/getFileByPath'; import getFileByPath from '../../src/uploads/getFileByPath';
import removeFiles from '../helpers/removeFiles'; import removeFiles from '../helpers/removeFiles';
@@ -14,7 +14,7 @@ export const audioSlug = 'audio';
const mockModulePath = path.resolve(__dirname, './mocks/mockFSModule.js'); const mockModulePath = path.resolve(__dirname, './mocks/mockFSModule.js');
export default buildConfig({ export default buildConfigWithDefaults({
admin: { admin: {
webpack: (config) => ({ webpack: (config) => ({
...config, ...config,

View File

@@ -1,4 +1,4 @@
import { buildConfig } from '../buildConfig'; import { buildConfigWithDefaults } from '../buildConfigWithDefaults';
import AutosavePosts from './collections/Autosave'; import AutosavePosts from './collections/Autosave';
import DraftPosts from './collections/Drafts'; import DraftPosts from './collections/Drafts';
import AutosaveGlobal from './globals/Autosave'; import AutosaveGlobal from './globals/Autosave';
@@ -7,7 +7,7 @@ import DraftGlobal from './globals/Draft';
import VersionPosts from './collections/Versions'; import VersionPosts from './collections/Versions';
import { draftSlug } from './shared'; import { draftSlug } from './shared';
export default buildConfig({ export default buildConfigWithDefaults({
collections: [ collections: [
AutosavePosts, AutosavePosts,
DraftPosts, DraftPosts,

3820
yarn.lock

File diff suppressed because it is too large Load Diff