diff --git a/.browserslistrc b/.browserslistrc
deleted file mode 100644
index 634aba4..0000000
--- a/.browserslistrc
+++ /dev/null
@@ -1,9 +0,0 @@
-# Browsers that we support
-
-# defaults # > 0.5%, last 2 versions, Firefox ESR, not dead
-
-last 3 versions
-last 3 iOS versions
-not IE 11 # remove `not` to support IE 11
-not dead # hide dead browsers
-> 5% # include if this is used by a lot of people, even though not in the above records
diff --git a/.eslintrc.js b/.eslintrc.cjs
similarity index 91%
rename from .eslintrc.js
rename to .eslintrc.cjs
index 66eab6d..6e5024c 100644
--- a/.eslintrc.js
+++ b/.eslintrc.cjs
@@ -5,9 +5,9 @@ module.exports = {
'plugin:lit-a11y/recommended',
],
parserOptions: {
- project: './tsconfig.eslint.json',
+ extraFileExtensions: ['.cjs'],
+ project: './tsconfig.json',
},
- ignorePatterns: ['src/server-bundle.ts', 'config', 'src/polyfills.js'],
rules: {
// Additions
'@typescript-eslint/consistent-type-imports': ['error'],
diff --git a/.github/workflow/docs.yml b/.github/workflow/docs.yml
new file mode 100644
index 0000000..8e3db8c
--- /dev/null
+++ b/.github/workflow/docs.yml
@@ -0,0 +1,35 @@
+name: docs
+
+on:
+ push:
+ branches: [main]
+
+jobs:
+ docs:
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Check out source
+ - uses: actions/checkout@v2
+ with:
+ fetch-depth: 0
+
+ - name: Setup Node.js
+ uses: actions/setup-node@v2
+ with:
+ node-version: '16'
+ cache: 'npm'
+
+ - name: Install npm packages
+ run: npm ci
+
+ - name: Build VuePress site
+ run: npm run docs:build
+
+ - name: Deploy to GitHub Pages
+ uses: crazy-max/ghaction-github-pages@v2
+ with:
+ target_branch: gh-pages
+ build_dir: docs/.vuepress/dist
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.gitignore b/.gitignore
index 1ebd5be..455ea1c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -33,6 +33,5 @@ yarn-error.log*
.stylelintcache
/docs/.vuepress/dist
-
-/scripts/devserver/output/*
-/scripts/devserver/mocks/*
+/docs/.vuepress/.cache
+/docs/.vuepress/.temp
diff --git a/.pota/commands/build.js b/.pota/commands/build.js
new file mode 100644
index 0000000..9ecfee9
--- /dev/null
+++ b/.pota/commands/build.js
@@ -0,0 +1,17 @@
+import { options as webpackSkeletonOptions } from "@pota/webpack-skeleton/.pota/commands/build.js"
+
+export { description, action } from "@pota/webpack-skeleton/.pota/commands/build.js"
+
+export const options = [
+ ...webpackSkeletonOptions,
+ {
+ option: '--preview',
+ description: 'Toggles support for building the preview',
+ },
+ {
+ option: '--mock-api',
+ description: 'Toggles support for building API mocks',
+ default: false
+ },
+];
+
diff --git a/.pota/commands/dev.js b/.pota/commands/dev.js
new file mode 100644
index 0000000..f63764d
--- /dev/null
+++ b/.pota/commands/dev.js
@@ -0,0 +1,13 @@
+import { options as webpackSkeletonOptions } from "@pota/webpack-skeleton/.pota/commands/dev.js"
+
+export { description, action } from "@pota/webpack-skeleton/.pota/commands/dev.js"
+
+export const options = [
+ ...webpackSkeletonOptions,
+ {
+ option: '--mock-api',
+ description: 'Toggles support for API mocks',
+ default: false
+ },
+];
+
diff --git a/.pota/config.js b/.pota/config.js
new file mode 100644
index 0000000..587e998
--- /dev/null
+++ b/.pota/config.js
@@ -0,0 +1,16 @@
+export default {
+ extends: "@pota/webpack-skeleton",
+ scripts: [
+ "dev",
+ "build",
+ "build:preview",
+ "storybook",
+ "storybook:mock-api",
+ "storybook:build",
+ "apply-storybook-patches",
+ "rsync",
+ "rsync:mocks",
+ "rsync:storybook",
+ ],
+ omit: ["public/index.html", "public/manifest.json", "public/robots.txt", "public/favicon.ico"],
+};
diff --git a/.pota/webpack/plugins/CopyEmittedAssetsPlugin.js b/.pota/webpack/plugins/CopyEmittedAssetsPlugin.js
new file mode 100644
index 0000000..d25fbde
--- /dev/null
+++ b/.pota/webpack/plugins/CopyEmittedAssetsPlugin.js
@@ -0,0 +1,31 @@
+import { join, dirname } from 'path';
+import { access, mkdir, writeFile } from 'fs/promises';
+
+const NS = "CopyEmittedAssetsPlugin";
+
+export default class CopyEmittedAssetsPlugin {
+ constructor(filter, outputPath) {
+ this.filter = filter;
+ this.outputPath = outputPath;
+ }
+
+ apply(compiler) {
+ compiler.hooks.assetEmitted.tapPromise(NS, async (file, { content }) => {
+ if (!this.filter.test(file)) return;
+
+ const newPath = join(this.outputPath, file);
+ const newPathDir = dirname(newPath);
+
+ try {
+ // check if the directory of the new path is accessible (i.e. exists)
+ await access(newPathDir);
+ } catch {
+ // assume that the directory does not exist and recursively create it
+ await mkdir(newPathDir, { recursive: true });
+ } finally {
+ // write the file to the new path
+ await writeFile(newPath, content);
+ }
+ });
+ }
+}
diff --git a/.pota/webpack/plugins/EmitMockMainPlugin.js b/.pota/webpack/plugins/EmitMockMainPlugin.js
new file mode 100644
index 0000000..e6e2ef4
--- /dev/null
+++ b/.pota/webpack/plugins/EmitMockMainPlugin.js
@@ -0,0 +1,25 @@
+const NS = "EmitMockMainPlugin";
+
+export default class EmitMockMainPlugin {
+ get source() {
+ return `
+import { execSync } from "child_process";
+
+execSync('npx @mediamonks/monck -u', { stdio: [0, 1, 2] });
+ `;
+ }
+
+ apply(compiler) {
+ const { webpack } = compiler;
+ const { Compilation, sources } = webpack;
+
+ compiler.hooks.thisCompilation.tap(NS, (compilation) => {
+ compilation.hooks.processAssets.tap(
+ { name: NS, stage: Compilation.PROCESS_ASSETS_STAGE_ADDITIONAL },
+ (assets) => {
+ assets["main.mjs"] = new sources.RawSource(this.source);
+ }
+ );
+ });
+ }
+}
diff --git a/.pota/webpack/plugins/MubanPagePlugin.js b/.pota/webpack/plugins/MubanPagePlugin.js
new file mode 100644
index 0000000..3c56daf
--- /dev/null
+++ b/.pota/webpack/plugins/MubanPagePlugin.js
@@ -0,0 +1,165 @@
+import { readFile } from "fs/promises";
+
+import requireFromString from "require-from-string";
+
+const NS = "MubanPagePlugin";
+
+function isString(value) {
+ return typeof value === "string";
+}
+
+function isFunction(value) {
+ return typeof value === "function";
+}
+
+function stringifyAttributes(attributes) {
+ return Object.entries(attributes)
+ .map(([key, value]) => `${String(key)}="${String(value)}"`)
+ .join(" ");
+}
+
+function convertObjectsToTags(objectOrArray, tag) {
+ const arr = Array.isArray(objectOrArray) ? objectOrArray : [objectOrArray];
+
+ switch (tag) {
+ case "link":
+ return arr.map((attributes) => ``);
+ case "meta":
+ return arr.map((attributes) => ``);
+ default:
+ return [];
+ }
+}
+
+function replaceTemplateVars(template, variables = {}) {
+ let updatedTemplate = String(template);
+
+ for (const [key, value] of Object.entries(variables)) {
+ updatedTemplate = updatedTemplate.replace(new RegExp(`{{${key}}}`, "g"), value);
+ }
+
+ return updatedTemplate;
+}
+
+function replaceTemplateTitle(template, title) {
+ return template.replace(/
(.*?)<\/title>/, (match, g0) => match.replace(g0, title));
+}
+
+function insertHeadTags(template, headTags) {
+ const headClosingTagIndex = template.indexOf("");
+
+ const beforeHeadClosingTag = template.slice(0, headClosingTagIndex);
+ const afterHeadClosingTag = template.slice(headClosingTagIndex); // includes the head closing tag
+
+ return [beforeHeadClosingTag, ...headTags, afterHeadClosingTag].join("\n");
+}
+
+export default class MubanPagePlugin {
+ options;
+ cache = new WeakMap();
+
+ constructor(options) {
+ this.options = options;
+ }
+
+ apply(compiler) {
+ const { webpack } = compiler;
+ const { sources, Compilation } = webpack;
+
+ // Specify the event hook to attach to
+ compiler.hooks.thisCompilation.tap(NS, (compilation) => {
+ compilation.hooks.processAssets.tapPromise(
+ { name: NS, stage: Compilation.PROCESS_ASSETS_STAGE_DERIVED },
+ async (assets) => {
+ const [chunk] = compilation.chunks;
+ const [file] = chunk.files;
+
+ const asset = assets[file];
+
+ if (!this.cache.has(asset)) {
+ const compilationHash = compilation.hash;
+
+ const publicPath = compilation.getAssetPath(compilation.outputOptions.publicPath, {
+ hash: compilationHash,
+ });
+
+ try {
+ const pageAssets = await this.generatePageAssets(asset.source(), publicPath, sources);
+
+ this.cache = new WeakMap();
+ this.cache.set(asset, pageAssets);
+ } catch (error) {
+ console.log();
+ console.error(`Error settings new page assets:`);
+ console.error(error);
+ }
+ }
+
+ for (const [page, source] of this.cache.get(asset)) {
+ assets[page] = source;
+ }
+ }
+ );
+ });
+ }
+
+ cachedTemplate = undefined;
+ async getHtmlTemplate() {
+ this.cachedTemplate =
+ this.cachedTemplate ?? (await readFile(this.options.template, { encoding: "utf-8" }));
+ return this.cachedTemplate;
+ }
+
+ async generatePageAssets(contextSource, publicPath, sources) {
+ const { pages, appTemplate } = this.getContextModule(contextSource);
+
+ const htmlTemplate = await this.getHtmlTemplate();
+
+ return Object.entries(pages)
+ .map(([page, m]) => {
+ const asset = `${page}.html`;
+
+ try {
+ let pageTemplate = replaceTemplateVars(htmlTemplate, {
+ content: appTemplate(m.data()),
+ publicPath,
+ });
+
+ if ("title" in m && isString(m.title)) {
+ pageTemplate = replaceTemplateTitle(pageTemplate, m.title);
+ }
+
+ const newHeadTags = [];
+
+ if ("meta" in m && isFunction(m.meta)) {
+ newHeadTags.push(...convertObjectsToTags(m.meta(), "meta"));
+ }
+ if ("link" in m && isFunction(m.link)) {
+ newHeadTags.push(...convertObjectsToTags(m.link(), "link"));
+ }
+
+ if (newHeadTags.length > 0) pageTemplate = insertHeadTags(pageTemplate, newHeadTags);
+
+ return [asset, new sources.RawSource(pageTemplate)];
+ } catch (error) {
+ console.log();
+ console.error(`Error occurred processing "${asset}":`);
+ console.error(error);
+ return null;
+ }
+ })
+ .filter(Boolean);
+ }
+
+ getContextModule(source) {
+ const fallback = { pages: {}, appTemplate: () => "" };
+
+ try {
+ return { ...fallback, ...requireFromString(source) };
+ } catch (error) {
+ console.error(error);
+
+ return fallback;
+ }
+ }
+}
diff --git a/.pota/webpack/webpack.config.js b/.pota/webpack/webpack.config.js
new file mode 100644
index 0000000..121829e
--- /dev/null
+++ b/.pota/webpack/webpack.config.js
@@ -0,0 +1,323 @@
+import { join, resolve, extname, basename } from 'path';
+
+import * as paths from '@pota/webpack-skeleton/.pota/webpack/paths.js';
+import { createFindPlugin } from '@pota/webpack-skeleton/.pota/webpack/util.js';
+import { Recursive } from '@pota/shared/fs';
+
+import MiniCssExtractPlugin from 'mini-css-extract-plugin';
+import CopyPlugin from 'copy-webpack-plugin';
+
+import historyApiFallback from 'connect-history-api-fallback';
+import { createMockMiddleware } from '@mediamonks/monck';
+
+import MubanPagePlugin from './plugins/MubanPagePlugin.js';
+import EmitMockMainPlugin from './plugins/EmitMockMainPlugin.js';
+
+const CSS_TEST = /\.css$/;
+const SCSS_TEST = /\.(scss|sass)$/;
+
+const MOCKS_DIR = resolve(paths.user, 'mocks');
+const MOCKS_OUTPUT_DIR = resolve(paths.user, 'dist', 'node');
+
+function isString(value) {
+ return typeof value === 'string';
+}
+
+function getNodeTargetRules(config) {
+ const imgTest = /\.(png|jpe?g|gif|webp|avif)(\?.*)?$/;
+ const svgTest = /\.(svg)(\?.*)?$/;
+ const mediaTest = /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/;
+ const tsTest = /\.tsx?$/;
+ const jsTest = /\.m?jsx?$/;
+
+ const permittedRules = [jsTest, tsTest, imgTest, svgTest, mediaTest].map((test) => String(test));
+
+ return [
+ {
+ oneOf: [
+ ...config.module.rules.filter((rule) => permittedRules.includes(String(rule.test))),
+ // `null-loader` will make sure to ignore any accidental imports to e.g. `.css` files
+ {
+ exclude: [/\.(js|mjs|ts)$/, /\.html$/, /\.json$/],
+ use: 'null-loader',
+ },
+ ],
+ },
+ ];
+}
+
+function createMainConfig(config, { mainName, pagesName, mockApi }) {
+ let { plugins } = config;
+
+ const isDev = config.mode
+ ? config.mode === 'development'
+ : process.env.NODE_ENV === 'development';
+
+ const findPlugin = createFindPlugin(config);
+ const htmlPlugin = findPlugin('HtmlWebpackPlugin');
+ const miniCssExtractPlugin = findPlugin('MiniCssExtractPlugin');
+
+ plugins = plugins.filter((plugin) => plugin !== htmlPlugin && plugin !== miniCssExtractPlugin);
+
+ const mocksDir = join(MOCKS_OUTPUT_DIR, './mocks');
+ const mocksHotUpdateDir = join(mocksDir, './static', './webpack');
+
+ /** @type {import('webpack').Configuration} */
+ return {
+ ...config,
+ name: mainName,
+ output: {
+ ...config.output,
+ filename: `static/chunks/[name].js`,
+ },
+ optimization: {
+ ...config.optimization,
+ runtimeChunk: undefined,
+ splitChunks: {
+ ...config.splitChunks,
+ cacheGroups: {
+ ...config.cacheGroups,
+ // but Muban is special and requires a single `main.css` file π
+ styles: {
+ name: 'main',
+ type: 'css/mini-extract',
+ chunks: 'all',
+ reuseExistingChunk: true,
+ enforce: true,
+ },
+ },
+ },
+ },
+ module: {
+ ...config.module,
+ // this overriding of `style-loader` to `MiniCssExtractPlugin.loader` would happen only during production
+ // but Muban is special and requires a single `main.css` file π
+ rules: config.module.rules.map((rule) => {
+ if ([String(CSS_TEST), String(SCSS_TEST)].includes(String(rule.test))) {
+ return {
+ ...rule,
+ use: rule.use.map((use) => {
+ if (use === 'style-loader') return MiniCssExtractPlugin.loader;
+ if (!isString(use) && 'loader' in use && use.loader === 'sass-loader') {
+ return {
+ ...use,
+ options: {
+ ...use.options,
+ // TODO: this is terribly inefficient, since we're creating a single .css file there should be a better way to add global styles
+ additionalData: `
+ @import "~seng-scss";
+ @import "@/styles/_global.scss";
+ `,
+ },
+ };
+ }
+ return use;
+ }),
+ };
+ }
+
+ return rule;
+ }),
+ },
+ devServer: {
+ ...config.devServer,
+ static: {
+ ...config.devServer.static,
+ serveIndex: false,
+ },
+ devMiddleware: {
+ writeToDisk(file) {
+ return file.startsWith(mocksDir) && !file.startsWith(mocksHotUpdateDir);
+ },
+ },
+ historyApiFallback: false,
+ onBeforeSetupMiddleware(devServer) {
+ if (!devServer) {
+ throw new Error('[onBeforeSetupMiddleware]: `devServer` is not defined');
+ }
+
+ if (mockApi) devServer.app.use('/api/', createMockMiddleware(mocksDir));
+
+ devServer.app.use('/', (req, res, next) => {
+ if (!devServer.stats) return next();
+
+ // find all of the `.html` assets generated by the "pages" compilation (the other config)
+ const pageAssets = Array.from(
+ devServer.stats.stats.find(({ compilation }) => compilation.name === pagesName)
+ .compilation.emittedAssets
+ ).filter((asset) => asset.endsWith('.html'));
+
+ // setup redirects from paths to html files e.g. `/my/favourite/page` to `/my/favourite/page.html`
+ return historyApiFallback({
+ rewrites: pageAssets.map((asset) => ({
+ from: new RegExp(`^\/${asset.replace('.html', '').replace('/index', '')}$`),
+ to: `/${asset}`,
+ })),
+ })(req, res, next);
+ });
+ },
+ },
+ plugins: [
+ ...plugins,
+ // this plugin is generally applied only during production builds, but Muban is special and requires a single `main.css` file π
+ new MiniCssExtractPlugin({
+ ignoreOrder: true,
+ filename: 'static/css/[name].css',
+ chunkFilename: `static/css/${isDev ? '[id]' : '[id].[contenthash]'}.css`,
+ }),
+ ],
+ };
+}
+function createPagesConfig(config, { pagesName, isDev }, options) {
+ const definePlugin = createFindPlugin(config)('DefinePlugin');
+
+ const source = join(paths.source, './pages');
+ const publicDir = join(source, './public');
+
+ /** @type {import('webpack').Configuration} */
+ return {
+ ...config,
+ name: pagesName, // required so the `devServer` can find the correct compilation
+ target: 'node',
+ mode: 'development', // we do not care about the size of the output, it just needs to be built fast
+ devtool: false, // source maps will not be used
+ entry: { pages: resolve(source, '_main.ts') },
+
+ cache: options.cache && {
+ type: 'filesystem',
+ name: `${pagesName}-${isDev ? 'development' : 'production'}`,
+ },
+
+ output: {
+ ...config.output,
+ // we are importing the module as a string, so we must bundle it as `commonjs`
+ chunkFormat: 'commonjs',
+ library: { type: 'commonjs' },
+ },
+ optimization: {
+ minimize: false,
+ moduleIds: 'named',
+ runtimeChunk: undefined,
+ },
+ module: {
+ ...config.module,
+ rules: getNodeTargetRules(config),
+ },
+ // we only care about compiling the `.ts` files in the `/src/pages` directory into `.html` files
+ plugins: [
+ definePlugin,
+ new MubanPagePlugin({ template: resolve(publicDir, 'index.html') }),
+ new CopyPlugin({
+ patterns: [
+ {
+ from: publicDir,
+ toType: 'dir',
+ globOptions: {
+ ignore: ['**/.*', resolve(publicDir, 'index.html')],
+ },
+ },
+ ],
+ }),
+ ],
+ };
+}
+
+async function createMockConfig(config, { mocksName, isDev }, options) {
+ const definePlugin = createFindPlugin(config)('DefinePlugin');
+
+ const entry = Object.fromEntries(
+ (await Recursive.readdir(MOCKS_DIR))
+ .filter((file) => extname(file) === '.ts' && !basename(file).startsWith('_'))
+ .map((file) => [basename(file, extname(file)), resolve(MOCKS_DIR, file)])
+ );
+
+ /** @type {import('webpack').Configuration} */
+ return {
+ ...config,
+ name: mocksName, // required so the `devServer` can find the correct compilation
+ target: 'node',
+ mode: 'development', // we do not care about the size of the output, it just needs to be built fast
+ devtool: false, // source maps will not be used
+
+ entry,
+
+ cache: options.cache && {
+ type: 'filesystem',
+ name: `${mocksName}-${isDev ? 'development' : 'production'}`,
+ },
+
+ output: {
+ ...config.output,
+ path: MOCKS_OUTPUT_DIR,
+ filename: './mocks/[name].mjs',
+ chunkFilename: `./mocks/[name].chunk.mjs`,
+ chunkFormat: 'commonjs',
+ module: true,
+ library: { type: 'module' },
+ },
+ experiments: { outputModule: true },
+ optimization: {
+ minimize: false,
+ moduleIds: 'named',
+ runtimeChunk: undefined,
+ },
+ module: {
+ ...config.module,
+ rules: [
+ {
+ oneOf: getNodeTargetRules(config)[0].oneOf.map((rule) => {
+ if ('include' in rule) {
+ return { ...rule, include: [rule.include, MOCKS_DIR] };
+ }
+
+ return rule;
+ }),
+ },
+ ],
+ },
+ plugins: [
+ definePlugin,
+ new CopyPlugin({
+ patterns: [
+ {
+ from: MOCKS_DIR,
+ to: join(MOCKS_OUTPUT_DIR, './mocks'),
+ globOptions: {
+ ignore: ['**/.*', '**/*.js', '**/*.ts'],
+ },
+ },
+ ],
+ }),
+ !isDev && new EmitMockMainPlugin(),
+ !isDev && new CopyEmittedAssetsPlugin(/^static\//, config.output.path),
+ ].filter(Boolean),
+ };
+}
+
+function parseOptions(options) {
+ let { preview = false, ['mock-api']: mockApi = false } = options;
+
+ if (preview === 'false') preview = false;
+ if (mockApi === 'false') mockApi = false;
+
+ return { preview, mockApi };
+}
+
+export default async function createConfig(config, options = {}) {
+ const isDev = (config.mode ?? process.env.NODE_ENV) === 'development';
+
+ const { preview, mockApi } = parseOptions(options);
+
+ const mainName = 'muban';
+ const pagesName = 'pages';
+ const mocksName = 'mocks';
+
+ /** @type {import('webpack').Configuration[]} */
+ return [
+ // the "main" configuration for bundling the muban app
+ createMainConfig(config, { mainName, pagesName, mockApi }),
+ // the "pages" configuration for bundling the static pages (also used to serve development pages)
+ (preview || isDev) && createPagesConfig(config, { pagesName, isDev }, options),
+ mockApi && (await createMockConfig(config, { mocksName, isDev }, options)),
+ ].filter(Boolean);
+}
diff --git a/.prettierignore b/.prettierignore
deleted file mode 100644
index 37b390d..0000000
--- a/.prettierignore
+++ /dev/null
@@ -1 +0,0 @@
-# Add paths to ignore like i.e. vendor files
diff --git a/.prettierrc b/.prettierrc
deleted file mode 100644
index f07793e..0000000
--- a/.prettierrc
+++ /dev/null
@@ -1,21 +0,0 @@
-{
- "printWidth": 100,
- "tabWidth": 2,
- "singleQuote": true,
- "trailingComma": "all",
- "proseWrap": "always",
- "overrides": [
- {
- "files": "*.json",
- "options": {
- "printWidth": 999999
- }
- },
- {
- "files": "*.scss",
- "options": {
- "singleQuote": false
- }
- }
- ]
-}
diff --git a/.storybook/main.cjs b/.storybook/main.cjs
new file mode 100644
index 0000000..99efecc
--- /dev/null
+++ b/.storybook/main.cjs
@@ -0,0 +1,59 @@
+const CSS_TEST = /\.css$/;
+const SCSS_TEST = /\.(scss|sass)$/;
+
+const ENABLE_MOCK_API_MIDDLEWARE = process.env.MOCK_API === "true";
+
+module.exports = {
+ core: {
+ builder: "webpack5",
+ },
+ stories: ["../src/**/*.stories.mdx", "../src/**/*.stories.@(js|jsx|ts|tsx)"],
+ staticDirs: ["../public", "../src/pages/public", "static"],
+ addons: ["@storybook/addon-essentials", "@mediamonks/muban-storybook-addon-transition"],
+ async webpackFinal(config) {
+ const { createFindPlugin } = await import("@pota/webpack-skeleton/.pota/webpack/util.js");
+
+ const [mubanConfig] = await getConfigs();
+
+ const findPlugin = createFindPlugin(mubanConfig);
+ const miniCssExtractLoader = findPlugin("MiniCssExtractPlugin").constructor.loader;
+
+ return {
+ ...config,
+ resolve: {
+ ...config.resolve,
+ alias: { ...config.resolve.alias, ...mubanConfig.resolve.alias },
+ },
+ module: {
+ ...config.module,
+ rules: mubanConfig.module.rules.map((rule) =>
+ // storybook needs the standard `style-loader`
+ [String(CSS_TEST), String(SCSS_TEST)].includes(String(rule.test))
+ ? {
+ ...rule,
+ use: rule.use.map((use) => (use === miniCssExtractLoader ? "style-loader" : use)),
+ }
+ : rule
+ ),
+ },
+ plugins: [...config.plugins, findPlugin("DefinePlugin")],
+ };
+ },
+ async managerWebpack(config) {
+ if (!ENABLE_MOCK_API_MIDDLEWARE) return config;
+
+ const [, , mocksConfig] = await getConfigs();
+
+ return [config, mocksConfig];
+ },
+};
+
+async function getConfigs() {
+ const { getNestedConfigs, createConfig } = await import(
+ "@pota/webpack-skeleton/.pota/webpack/util.js"
+ );
+
+ return createConfig(await getNestedConfigs(), {
+ "mock-api": ENABLE_MOCK_API_MIDDLEWARE,
+ });
+}
diff --git a/.storybook/main.ts b/.storybook/main.ts
deleted file mode 100644
index 5899e55..0000000
--- a/.storybook/main.ts
+++ /dev/null
@@ -1,22 +0,0 @@
-import { paths } from '../config/paths';
-
-export default {
- stories: ['../src/**/*.stories.mdx', '../src/**/*.stories.@(js|jsx|ts|tsx)'],
- addons: [
- // "@storybook/addon-links",
- // "@storybook/addon-essentials"
- {
- name: '@storybook/preset-scss',
- options: {
- sassLoaderOptions: {
- additionalData: `
- @import "~seng-scss";
- @import "${paths.srcPath
- .substring(paths.projectDir.length)
- .replace(/\\/g, '/')}/styles/_global.scss";
- `,
- },
- },
- },
- ],
-};
diff --git a/.storybook/manager.js b/.storybook/manager.js
new file mode 100644
index 0000000..9c9f263
--- /dev/null
+++ b/.storybook/manager.js
@@ -0,0 +1,6 @@
+import { addons } from '@storybook/addons';
+import mmTheme from './mm-theme';
+
+addons.setConfig({
+ theme: mmTheme,
+});
diff --git a/.storybook/middleware.js b/.storybook/middleware.js
index 001c0e3..070d429 100644
--- a/.storybook/middleware.js
+++ b/.storybook/middleware.js
@@ -1,8 +1,12 @@
-import { createMockMiddleWare } from '@mediamonks/monck';
-import { paths } from '../config/paths';
+const { resolve } = require("path");
-const middleware = (router) => {
- router.use('/api/', createMockMiddleWare(paths.mockPath));
- router.use('/api/', (req, res) => res.sendStatus(404));
-};
-export default middleware;
+const ENABLE_MOCK_API_MIDDLEWARE = process.env.MOCK_API === "true";
+const MOCKS_OUTPUT_DIR = resolve(process.cwd(), 'dist', 'node', "mocks");
+
+module.exports = async router => {
+ if (ENABLE_MOCK_API_MIDDLEWARE) {
+
+ const { createMockMiddleware } = await import('@mediamonks/monck');
+ router.use('/api/', createMockMiddleware(MOCKS_OUTPUT_DIR));
+ }
+}
diff --git a/.storybook/mm-theme.js b/.storybook/mm-theme.js
new file mode 100644
index 0000000..dbc0521
--- /dev/null
+++ b/.storybook/mm-theme.js
@@ -0,0 +1,8 @@
+import { create } from '@storybook/theming';
+
+export default create({
+ base: 'dark',
+ brandTitle: 'Media.Monks',
+ brandUrl: 'https://media.monks.com/',
+ brandImage: './mm-theme-brand-logo.png',
+});
diff --git a/.storybook/package.json b/.storybook/package.json
new file mode 100644
index 0000000..0967ef4
--- /dev/null
+++ b/.storybook/package.json
@@ -0,0 +1 @@
+{}
diff --git a/.storybook/preview.js b/.storybook/preview.js
index 8408271..d391458 100644
--- a/.storybook/preview.js
+++ b/.storybook/preview.js
@@ -1,9 +1,9 @@
-import '../src/styles/main.scss';
-
export const parameters = {
actions: { argTypesRegex: '^on[A-Z].*' },
+ controls: {
+ matchers: {
+ color: /(background|color)$/i,
+ date: /Date$/,
+ },
+ },
};
-
-// const sprite = document.createElement('img');
-// sprite.src = require('bootstrap-icons/bootstrap-icons.svg');
-// document.body.appendChild(sprite);
diff --git a/.storybook/static/mm-theme-brand-logo.png b/.storybook/static/mm-theme-brand-logo.png
new file mode 100644
index 0000000..e38861f
Binary files /dev/null and b/.storybook/static/mm-theme-brand-logo.png differ
diff --git a/.storybook/webpack.config.ts b/.storybook/webpack.config.ts
deleted file mode 100644
index 353fcdb..0000000
--- a/.storybook/webpack.config.ts
+++ /dev/null
@@ -1,65 +0,0 @@
-import { getClientEnvironment } from '../config/env';
-import { paths } from '../config/paths';
-
-// webpack 4 version of the plugin
-import webpack from '@storybook/core/node_modules/webpack';
-
-// We will provide `paths.publicUrlOrPath` to our app
-// as %PUBLIC_URL% in `index.html` and `process.env.PUBLIC_URL` in JavaScript.
-// Omit trailing slash as %PUBLIC_URL%/xyz looks better than %PUBLIC_URL%xyz.
-// Get environment variables to inject into our app.
-const env = getClientEnvironment(paths.publicPath.slice(0, -1));
-
-/**
- * NOTE: Storybook still uses webpack 4, so might not be 1-on-1 compatible with
- * the project webpack config, which uses webpack 5!!
- */
-export default ({ config }) => {
- // Add the src path so we can load the assets
- config.resolve.modules = [paths.srcPath, ...config.resolve.modules];
-
- config.plugins.push(new webpack.DefinePlugin(env.stringified));
-
- // remove this rule that deals with SVGs and media files
- config.module.rules = config.module.rules.filter(
- (rule) => !(String(rule.test).includes('svg') || String(rule.test).includes('mp4')),
- );
- // Add the rule back without the svg in it, jep, a bit hacky
- config.module.rules.push({
- test: /\.(ico|jpg|jpeg|png|gif|eot|otf|webp|ttf|woff|woff2|cur|ani|pdf)(\?.*)?$/,
- loader: require.resolve('url-loader'),
- options: {
- limit: 10000,
- name: 'static/media/[name].[hash:8].[ext]',
- esModule: false,
- },
- });
-
- // Re-add the media rule but with esModule turned off, to be in sync with the other assets
- config.module.rules.push({
- test: /\.(mp4|webm|wav|mp3|m4a|aac|oga)(\?.*)?$/,
- loader: require.resolve('url-loader'),
- options: {
- limit: 10000,
- name: 'static/media/[name].[hash:8].[ext]',
- esModule: false,
- },
- });
-
- // Add a loader for the svg's
- config.module.rules.push({
- test: /\.svg$/,
- oneOf: [
- {
- resourceQuery: /inline/,
- use: [{ loader: 'raw-loader', options: { esModule: false } }, { loader: 'svgo-loader' }],
- },
- {
- use: [{ loader: 'url-loader' }, { loader: 'svgo-loader' }],
- },
- ],
- });
-
- // Return the altered config
- return config;
-};
diff --git a/.stylelintrc b/.stylelintrc
deleted file mode 100644
index 9ec71e9..0000000
--- a/.stylelintrc
+++ /dev/null
@@ -1,3 +0,0 @@
-{
- "extends": "stylelint-config-recommended-scss"
-}
diff --git a/README.md b/README.md
index f903bb0..949296f 100644
--- a/README.md
+++ b/README.md
@@ -1,62 +1,8 @@
-# π Welcome to your new awesome project!
+# muban-skeleton [![downloads](https://badgen.now.sh/npm/dm/@muban/skeleton)](https://npmjs.org/package/@muban/skeleton) ![extends](https://badgen.net/badge/extends/@pota%2Fwebpack-skeleton/blue)
-## Scripts
+This project follows the [MediaMonks Frontend Coding Standards](https://github.com/mediamonks/frontend-coding-standards).
-### `yarn dev`
-
-Your goto script when running local development against local page templates with live and hot reloading when any of
-your code changes.
-
-- runs the code bundle with `WebpackDevServer`, hot-reloading your JS and CSS changes
-- runs the server bundle to generate templates and serves them from express, live-reloading your HTML changes
-
-The dev server runs on `http://localhost:9000`.
-
-### `yarn build`
-
-Creates a distribution build that outputs the JS and CSS files. Used in CI to deploy to your production websites.
-
-Append `--watch` to the command to start this webpack in watch mode, enjoying super fast recompilations when your
-local files changes.
-
-When the `MUBAN_ANALYZE` environment variable is set, it will generate a bundle-analyzer output
-report.
-
-### `yarn build:preview`
-
-Generates a full preview package including generated HTML files to upload to a preview server that's not connected to
-any backend.
-
-### `yarn build:debug`
-
-Generates a quick debug build without any minification and other optimizations. Useful for quick integration tests
-where you're not deploying to production yet, but want to see your changes on a (local) integration server as fast
-as possible.
-
-Append `--watch` to the command to start this webpack in watch mode, enjoying super fast recompilations when your
-local files changes. Very useful for live local development against your CMS rendered pages.
-
-### `yarn storybook`
-
-Develop and test your components in storybook.
-
-### `yarn storybook:build`
-
-Make a deployable storybook build to showcase your components to others.
-
-### `yarn preview`
-
-Start a local server to see the result of `yarn build:preview`
-
-The dev server runs on `http://localhost:9001`.
-
-### `yarn test`
-
-> TODO
-
-### `yarn test:e2e`
-
-> TODO
+Documentation on the `muban-skeleton` can be found on https://mubanjs.github.io/muban-skeleton/.
### Content Security Policy ([CSP](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP))
diff --git a/STATUS.MD b/STATUS.MD
deleted file mode 100644
index 2ddd661..0000000
--- a/STATUS.MD
+++ /dev/null
@@ -1,89 +0,0 @@
-# Muban Skeleton
-
-## Production build
-
-Done
-+ bundle JS and CSS
-+ support lazy loading / code splitting
-+ 3 build types
- + dist mode (single build, output on disk)
- + dev mode (run webpack dev server and serve files from memory, including HTMl generation)
- / watch mode (output on disk, but watch change for a quick rebuild)
-+ export generated HTML pages
-
-Todo
-- exclude all templates - except the ones being actually used
-
-### Dev mode
-
-Done
-+ run express server to server HTML and js/css
-+ output HTML from express, not from JS, to better simulate a built package
-+ integrate webpack-dev-middleware and webpack-hot-middleware
-
-Todo
-- implement live-reload for HTML reload (https://github.com/mubanjs/muban-skeleton/projects/1#card-58757198)
-- implement proper hot-reloading for JS components - or maybe just reload the page? (https://github.com/mubanjs/muban-skeleton/projects/1#card-58757255)
-- I noticed that the hot reloading for the new muban wouldn't work sometimes. E.g. when a
- variable type error is fixed, it would continue to show the error. Only upon restarting the
- server would it go away. (this is for server templates)
-
-
-### Template rendering
-
-Done
-+ root template is always the "index.html" that includes the head and body with default info and app container
-+ then there is a default layout template that can render blocks, with an optional header and footer
-+ user can create their own layouts for other pages
-+ each page can optionally specify to choose a custom layout to render
-+ a layout or template decides what to render based on the provided data on the page
-
-### Pages
-
-Done
-+ a page is a TS file that exports a data object, as an object or a function
-
-Todo
-- build up a "context" object and pass this to each page's `data` function
- https://github.com/mubanjs/muban-skeleton/projects/1#card-58757108
-- besides page data, allow for setting other info like metadata (title/description/og:tags) https://github.com/mubanjs/muban-skeleton/projects/1#card-58756985
-
-- multilanguage
-
-### Routing
-
-Both during dev mode, and when generating build pages, we need to know what Pages exist, and what to render.
-
-Done
-+ when no "index" file on disk is present, render auto-index instead
-+ also support pages in folders
-+ files on disk should be pages
- + urls should include .html
-+ folder should redirect to index.html in the folder
-
-Todo
-- make "router" available to templates? (https://github.com/mubanjs/muban-skeleton/projects/1#card-58757446)
-
-### Theming
-
-Todo
-https://github.com/mubanjs/muban-skeleton/projects/1#card-58757391
-- support for themes/styles/variables
-- make separate theme build
-- other exceptions per theme (html? js?)
-- pages per theme? theme variables?
-
-#### Optional
-
-Done
-+ leave CSS bundled with JS files to reduce initial css load size (turned off again)
-
-Progress
-- API mocking
-
-Todo
-- create auto-index page (https://github.com/mubanjs/muban-skeleton/projects/1#card-58757343)
-- export only JS / CSS solo
-- typed API ??
-
-- check non-zero error codes on build
diff --git a/babel.config.js b/babel.config.js
deleted file mode 100644
index 71ec882..0000000
--- a/babel.config.js
+++ /dev/null
@@ -1,172 +0,0 @@
-/* eslint-disable global-require,import/no-extraneous-dependencies */
-
-const path = require('path');
-
-const absoluteRuntimePath = path.dirname(require.resolve('@babel/runtime/package.json'));
-
-module.exports = function(api) {
- api.cache(!api.env('production'));
-
- return {
- presets: [
- [
- '@babel/preset-env',
- {
- // The starting point where the config search for browserslist will start,
- // and ascend to the system root until found.
- configPath: __dirname,
-
- // By default, @babel/preset-env (and Babel plugins in general) grouped ECMAScript syntax
- // features into collections of closely related smaller features. These groups can be
- // large and include a lot of edge cases, for example "function arguments" includes
- // destructured, default and rest parameters. From this grouping information,
- // Babel enables or disables each group based on the browser support target you specify
- // to @babel/preset-envβs targets option.
- //
- // When this option is enabled, @babel/preset-env tries to compile the broken syntax to
- // the closest non-broken modern syntax supported by your target browsers.
- // Depending on your targets and on how many modern syntax you are using,
- // this can lead to a significant size reduction in the compiled app.
- // This option merges the features of @babel/preset-modules without having
- // to use another preset.
- bugfixes: true,
-
- // Setting this to false will preserve ES modules. Use this only if you intend to ship
- // native ES Modules to browsers. If you are using a bundler with Babel,
- // the default modules: "auto" is always preferred.
- modules: 'auto',
-
- // This option configures how @babel/preset-env handles polyfills.
- // When either the `usage` or `entry` options are used, @babel/preset-env will add direct
- // references to core-js modules as bare imports (or requires). This means core-js will
- // be resolved relative to the file itself and needs to be accessible.
- //
- // Since @babel/polyfill was deprecated in 7.4.0, we recommend directly adding core-js
- // and setting the version via the corejs option.
- useBuiltIns: 'entry',
-
- // This option only has an effect when used alongside `useBuiltIns: usage` or
- // `useBuiltIns: entry`, and ensures @babel/preset-env injects the polyfills
- // supported by your core-js version.
- // By default, only polyfills for stable ECMAScript features are injected:
- // if you want to polyfill proposals, you can directly import a proposal polyfill
- // inside /src/polyfills.js: import "core-js/proposals/string-replace-all".
- corejs: { version: "3.9" },
-
- // An array of plugins to always exclude/remove.
- // This option is useful for "blacklisting" a transform like
- // @babel/plugin-transform-regenerator if you don't use generators and don't want to
- // include regeneratorRuntime (when using useBuiltIns) or for using another plugin like
- // fast-async instead of Babel's async-to-gen.
- exclude: [
- // Exclude transforms that make all code slower
- 'transform-typeof-symbol',
-
- // // we don't use generators or async/await by default
- // 'transform-regenerator',
- //
- // // we don't use typed arrays by default
- // 'es6.typed.*',
- //
- // // we don't use reflect by default
- // 'es6.reflect.*',
- //
- // // we don't use symbols by default
- // 'es6.symbol',
- //
- // // we don't use advanced regexps by default
- // 'es6.regexp.*',
- //
- // // we don't use advanced math by default
- // 'es6.math.acosh',
- // 'es6.math.asinh',
- // 'es6.math.atanh',
- // 'es6.math.cbrt',
- // 'es6.math.clz32',
- // 'es6.math.cosh',
- // 'es6.math.expm1',
- // 'es6.math.fround',
- // 'es6.math.hypot',
- // 'es6.math.imul',
- // 'es6.math.log1p',
- // 'es6.math.log10',
- // 'es6.math.log2',
- // 'es6.math.sign',
- // 'es6.math.sinh',
- // 'es6.math.tanh',
- // 'es6.math.trunc',
- //
- // // we don't use maps and sets by default
- // 'es6.map',
- // 'es6.set',
- // 'es6.weak-map',
- // 'es6.weak-set',
- //
- // // Funky unused HTML string methods
- // 'es6.string.anchor',
- // 'es6.string.big',
- // 'es6.string.blink',
- // 'es6.string.bold',
- // 'es6.string.code-point-at',
- // 'es6.string.fixed',
- // 'es6.string.fontcolor',
- // 'es6.string.fontsize',
- // 'es6.string.from-code-point',
- // 'es6.string.italics',
- // 'es6.string.iterator',
- // 'es6.string.link',
- ],
- },
- ],
- [require('@babel/preset-typescript').default],
- ],
- plugins: [
- // class { handleClick = () => { } }
- // Enable loose mode to use assignment instead of defineProperty
- // See discussion in https://github.com/facebook/create-react-app/issues/4263
- [
- require('@babel/plugin-proposal-class-properties').default,
- {
- loose: true,
- },
- ],
-
- // Adds Numeric Separators
- require('@babel/plugin-proposal-numeric-separator').default,
-
- // Polyfills the runtime needed for async/await, generators, and friends
- // https://babeljs.io/docs/en/babel-plugin-transform-runtime
- [
- require('@babel/plugin-transform-runtime').default,
- {
- corejs: false,
- helpers: true,
- // By default, babel assumes babel/runtime version 7.0.0-beta.0,
- // explicitly resolving to match the provided helper functions.
- // https://github.com/babel/babel/issues/10261
- version: require('@babel/runtime/package.json').version,
- regenerator: true,
- // https://babeljs.io/docs/en/babel-plugin-transform-runtime#useesmodules
- // We should turn this on once the lowest version of Node LTS
- // supports ES Modules.
- useESModules: true, // TODO disable for testing?
- // Undocumented option that lets us encapsulate our runtime, ensuring
- // the correct version is used
- // https://github.com/babel/babel/blob/090c364a90fe73d36a30707fc612ce037bdbbb24/packages/babel-plugin-transform-runtime/src/index.js#L35-L42
- absoluteRuntime: absoluteRuntimePath,
- },
- ],
- 'lodash',
- '@babel/plugin-syntax-dynamic-import',
- '@babel/plugin-syntax-import-meta',
- '@babel/plugin-proposal-json-strings',
- ['@babel/plugin-proposal-decorators', { legacy: true }],
- '@babel/plugin-proposal-function-sent',
- '@babel/plugin-proposal-export-namespace-from',
- '@babel/plugin-proposal-throw-expressions',
-
- // needed to register muban-components
- '@babel/plugin-transform-react-display-name',
- ],
- };
-};
diff --git a/config/env.ts b/config/env.ts
deleted file mode 100644
index 573d994..0000000
--- a/config/env.ts
+++ /dev/null
@@ -1,97 +0,0 @@
-'use strict';
-
-import fs from 'fs';
-import path from 'path';
-import { paths } from './paths';
-
-// Make sure that including paths.js after env.js will read .env variables.
-delete require.cache[require.resolve('./paths')];
-
-const NODE_ENV = process.env.NODE_ENV;
-if (!NODE_ENV) {
- throw new Error('The NODE_ENV environment variable is required but was not specified.');
-}
-
-// https://github.com/bkeepers/dotenv#what-other-env-files-can-i-use
-const dotenvFiles = [
- `${paths.dotenv}.${NODE_ENV}.local`,
- // Don't include `.env.local` for `test` environment
- // since normally you expect tests to produce the same
- // results for everyone
- NODE_ENV !== 'test' && `${paths.dotenv}.local`,
- `${paths.dotenv}.${NODE_ENV}`,
- paths.dotenv,
-].filter(Boolean) as Array;
-
-// Load environment variables from .env* files. Suppress warnings using silent
-// if this file is missing. dotenv will never modify any environment variables
-// that have already been set. Variable expansion is supported in .env files.
-// https://github.com/motdotla/dotenv
-// https://github.com/motdotla/dotenv-expand
-dotenvFiles.forEach((dotenvFile) => {
- if (fs.existsSync(dotenvFile)) {
- require('dotenv-expand')(
- require('dotenv').config({
- path: dotenvFile,
- }),
- );
- }
-});
-
-// We support resolving modules according to `NODE_PATH`.
-// This lets you use absolute paths in imports inside large monorepos:
-// https://github.com/facebook/create-react-app/issues/253.
-// It works similar to `NODE_PATH` in Node itself:
-// https://nodejs.org/api/modules.html#modules_loading_from_the_global_folders
-// Note that unlike in Node, only *relative* paths from `NODE_PATH` are honored.
-// Otherwise, we risk importing Node.js core modules into an app instead of webpack shims.
-// https://github.com/facebook/create-react-app/issues/1023#issuecomment-265344421
-// We also resolve them to make sure all tools using them work consistently.
-const appDirectory = fs.realpathSync(process.cwd());
-process.env.NODE_PATH = (process.env.NODE_PATH || '')
- .split(path.delimiter)
- .filter((folder) => folder && !path.isAbsolute(folder))
- .map((folder) => path.resolve(appDirectory, folder))
- .join(path.delimiter);
-
-// Grab NODE_ENV and MUBAN_* environment variables and prepare them to be
-// injected into the application via DefinePlugin in webpack configuration.
-const MUBAN = /^MUBAN_/i;
-
-export function getClientEnvironment(publicPath) {
- const raw = Object.keys(process.env)
- .filter((key) => MUBAN.test(key))
- .reduce(
- (env, key) => {
- env[key] = process.env[key];
- return env;
- },
- {
- // Useful for determining whether weβre running in production mode.
- // Most importantly, it switches React into the correct mode.
- NODE_ENV: process.env.NODE_ENV || 'development',
- // Useful for resolving the correct path to static assets in `public`.
- // For example, .
- // This should only be used as an escape hatch. Normally you would put
- // images into the `src` and `import` them in code to get their paths.
- PUBLIC_PATH: publicPath,
- // We support configuring the sockjs pathname during development.
- // These settings let a developer run multiple simultaneous projects.
- // They are used as the connection `hostname`, `pathname` and `port`
- // in webpackHotDevClient. They are used as the `sockHost`, `sockPath`
- // and `sockPort` options in webpack-dev-server.
- WDS_SOCKET_HOST: process.env.WDS_SOCKET_HOST,
- WDS_SOCKET_PATH: process.env.WDS_SOCKET_PATH,
- WDS_SOCKET_PORT: process.env.WDS_SOCKET_PORT,
- },
- );
- // Stringify all values so we can feed into webpack DefinePlugin
- const stringified = {
- 'process.env': Object.keys(raw).reduce((env, key) => {
- env[key] = JSON.stringify(raw[key]);
- return env;
- }, {}),
- };
-
- return { raw, stringified };
-}
diff --git a/config/paths.ts b/config/paths.ts
deleted file mode 100644
index bb3a0b3..0000000
--- a/config/paths.ts
+++ /dev/null
@@ -1,67 +0,0 @@
-import path from 'path';
-const argv = require('yargs').argv;
-
-const projectDir = path.resolve(__dirname, '../');
-const srcPath = path.resolve(projectDir, './src');
-const distPath = path.resolve(projectDir, './dist');
-
-const resolveProject = (relativePath) => path.resolve(projectDir, relativePath);
-const resolveSource = (relativePath) => path.resolve(srcPath, relativePath);
-const resolveDist = (relativePath) => path.resolve(distPath, relativePath);
-
-const publicPath = getPublicPath('/');
-console.log('publicPath', publicPath);
-
-export const paths = {
- dotenv: resolveProject('.env'),
- packageJson: resolveProject('./package.json'),
- nodeModules: resolveProject('./node_modules'),
- projectDir,
- srcPath,
- distPath,
- publicPath,
- serverBundleEntry: path.resolve(srcPath, './server-bundle.ts'),
- distSitePath: resolveDist('./site'),
- distSiteStaticPath: resolveDist('./site/static'),
- distMockNodePath: resolveDist('./node'),
- mockPath: resolveProject('./mocks'),
- webpackClientConfig: resolveProject('./config/webpack.config.ts'),
- webpackServerConfig: resolveProject('./scripts/devserver/webpack.config.ts'),
- serverBundleOutputDir: resolveProject('./scripts/devserver/output'),
- serverBundleOutputEntry: resolveProject('./scripts/devserver/output/main.js'),
- devIndex: resolveProject('./scripts/devserver/index.html'),
- distIndex: resolveProject('./scripts/devserver/index.html'),
- webpackAssetPath: resolveSource('./assets'), // required through webpack
- webpackAssetFonts: resolveSource('./assets/fonts'),
- webpackAssetImages: resolveSource('./assets/images'),
- publicAssetPath: resolveSource('./public'), // CopyWebpackPlugin
- pagesPath: resolveSource('./pages'), // Only Dev and Preview
- pagesAssetPath: resolveSource('./pages/static'), // Only Dev and Preview
- tsConfig: resolveProject('./tsconfig.json'),
-};
-
-function getPublicPath(defaultPublicPath) {
- let publicPath = defaultPublicPath;
-
- if (argv.publicPath) {
- // TODO: currently not supported when using webpack-cli
- // since that will validate any parameter
- // Use process.env.PUBLIC_PATH instead
- publicPath = argv.PUBLIC_PATH;
- console.log('Using publicPath from "--publicPath"');
- } else if (process.env.PUBLIC_PATH) {
- publicPath = process.env.PUBLIC_PATH;
- console.log('Using publicPath from "process.env.PUBLIC_PATH"');
- }
-
- // force leading / if not relative with '.'
- if (!publicPath.startsWith('/') && !publicPath.startsWith('.')) {
- publicPath = `/${publicPath}`;
- }
- // force trailing /
- if (!publicPath.endsWith('/')) {
- publicPath = `${publicPath}/`;
- }
-
- return publicPath;
-}
diff --git a/config/tsconfig.webpack.json b/config/tsconfig.webpack.json
deleted file mode 100644
index 9c14c0c..0000000
--- a/config/tsconfig.webpack.json
+++ /dev/null
@@ -1,8 +0,0 @@
-{
- "compilerOptions": {
- "module": "commonjs",
- "target": "es5",
- "esModuleInterop": true,
- "allowSyntheticDefaultImports": true
- }
-}
\ No newline at end of file
diff --git a/config/webpack.config.ts b/config/webpack.config.ts
deleted file mode 100644
index 82744fe..0000000
--- a/config/webpack.config.ts
+++ /dev/null
@@ -1,475 +0,0 @@
-import fs from 'fs';
-import path from 'path';
-import resolve from 'resolve';
-import webpack from 'webpack';
-import postcssNormalize from 'postcss-normalize';
-import CssMinimizerPlugin from 'css-minimizer-webpack-plugin';
-import CopyWebpackPlugin from 'copy-webpack-plugin';
-import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer';
-import MiniCssExtractPlugin from 'mini-css-extract-plugin';
-import TerserPlugin from 'terser-webpack-plugin';
-import CaseSensitivePathsPlugin from 'case-sensitive-paths-webpack-plugin';
-import ModuleNotFoundPlugin from 'react-dev-utils/ModuleNotFoundPlugin';
-import ForkTsCheckerWebpackPlugin from 'react-dev-utils/ForkTsCheckerWebpackPlugin';
-import typescriptFormatter from 'react-dev-utils/typescriptFormatter';
-import { getClientEnvironment } from './env';
-
-import { paths } from './paths';
-
-const isPreview = process.env.MUBAN_PREVIEW === 'true';
-const analyze = process.env.MUBAN_ANALYZE === 'true';
-
-// Check if TypeScript is setup
-const useTypeScript = fs.existsSync(paths.tsConfig);
-
-// Source maps are resource heavy and can cause out of memory issue for large source files.
-const shouldUseSourceMap = process.env.GENERATE_SOURCEMAP === 'true';
-
-const appPackageJson = require(paths.packageJson);
-
-// We will provide `paths.publicUrlOrPath` to our app
-// as %PUBLIC_URL% in `index.html` and `process.env.PUBLIC_URL` in JavaScript.
-// Omit trailing slash as %PUBLIC_URL%/xyz looks better than %PUBLIC_URL%xyz.
-// Get environment variables to inject into our app.
-const env = getClientEnvironment(paths.publicPath.slice(0, -1));
-
-module.exports = function () {
- // console.log('webpack env', webpackEnv, argv);
- const isEnvProduction = process.env.NODE_ENV === 'production';
- const isEnvDevelopment = process.env.NODE_ENV !== 'production';
- const mode = isEnvProduction ? 'production' : isEnvDevelopment && 'development';
-
- console.log('mode', mode);
-
- return {
- mode,
- // Stop compilation early in production
- bail: isEnvProduction,
- devtool: isEnvProduction
- ? shouldUseSourceMap
- ? 'source-map'
- : false
- : isEnvDevelopment && 'cheap-module-source-map',
- // TODO: create separate "preview" entry for the auto-index
- entry: path.resolve(paths.srcPath, './index.ts'),
- output: {
- // The build folder.
- path: path.resolve(paths.distSitePath, '.'), // change the sub folders in the respective loaders
- // Add /* filename */ comments to generated require()s in the output.
- pathinfo: isEnvDevelopment,
- // There will be one main bundle, and one file per asynchronous chunk.
- // In development, it does not produce real files.
- // For consistent inclusions in server-generated pages, we don't add a [contenthash]
- // in production either.
- filename: 'static/js/[name].js',
- // There are also additional JS chunk files if you use code splitting.
- chunkFilename: `static/js/[name]${isEnvProduction ? '.[contenthash:8]' : ''}.chunk.js`,
- // webpack uses `publicPath` to determine where the app is being served from.
- // It requires a trailing slash, or the file assets will get an incorrect path.
- // TODO: add info about how to change this
- publicPath: paths.publicPath,
- // Point sourcemap entries to original disk location (format as URL on Windows)
- devtoolModuleFilenameTemplate: isEnvProduction
- ? (info) => path.relative(paths.srcPath, info.absoluteResourcePath).replace(/\\/g, '/')
- : isEnvDevelopment &&
- ((info) => path.resolve(info.absoluteResourcePath).replace(/\\/g, '/')),
- // this defaults to 'window', but by setting it to 'this' then
- // module chunks which are built will work in web workers as well.
- globalObject: 'this',
- },
-
- resolve: {
- extensions: ['.ts', '.js'],
- plugins: [
- // Adds support for installing with Plug'n'Play, leading to faster installs and adding
- // guards against forgotten dependencies and such.
- // PnpWebpackPlugin,
- // Prevents users from importing files from outside of src/ (or node_modules/).
- // This often causes confusion because we only process files within src/ with babel.
- // To fix this, we prevent you from importing files out of src/ -- if you'd like to,
- // please link the files into your node_modules/ and let module-resolution kick in.
- // Make sure your source files are compiled, as they will not be processed in any way.
- // TODO: doesn't work with mini-css-extract-plugin
- // new ModuleScopePlugin(paths.srcPath, [
- // paths.packageJson,
- // ]),
- ],
- modules: [paths.srcPath, 'node_modules'],
- },
- resolveLoader: {
- plugins: [
- // Also related to Plug'n'Play, but this time it tells webpack to load its loaders
- // from the current package.
- // PnpWebpackPlugin.moduleLoader(module),
- ],
- },
-
- module: {
- rules: [
- {
- // "oneOf" will traverse all following loaders until one will
- // match the requirements. When no loader matches it will fall
- // back to the "file" loader at the end of the loader list.
- oneOf: [
- {
- test: /\.js$/,
- enforce: 'pre',
- use: [require.resolve('source-map-loader')],
- },
- // Process application JS with Babel.
- // The preset includes JSX, Flow, TypeScript, and some ESnext features.
- {
- test: /\.(js|mjs|ts)$/,
- include: paths.srcPath,
- loader: require.resolve('babel-loader'),
- options: {
- // This is a feature of `babel-loader` for webpack (not Babel itself).
- // It enables caching results in ./node_modules/.cache/babel-loader/
- // directory for faster rebuilds.
- cacheDirectory: true,
- // See #6846 for context on why cacheCompression is disabled
- cacheCompression: false,
- compact: isEnvProduction,
- },
- },
- // {
- // test: /\.(ts|tsx)$/,
- // loader: require.resolve('ts-loader'),
- // include: [paths.srcPath],
- // exclude: [/node_modules/],
- // },
- // "postcss" loader applies autoprefixer to our CSS.
- // "css" loader resolves paths in CSS and adds assets as dependencies.
- // "style" loader turns CSS into JS modules that inject