Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor to use Pota #25

Merged
merged 38 commits into from Jan 7, 2022
Merged
Show file tree
Hide file tree
Changes from 37 commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
e896af2
replace existing skeleton with basic pota implementation
Dec 2, 2021
675c5e0
bump "@muban/storybook" to v7.0.0-alpha.15
Dec 6, 2021
6663dfb
remove story context patch as "@muban/storybook" 7.0.0-alpha.15 fixed…
Dec 6, 2021
12a52e9
bump "@storybook/builder-webpack5" & "@storybook/manager-webpack5" to…
Dec 6, 2021
b5202a0
remove name length check patch as "@storybook/core-events" 6.4.1 fixe…
Dec 6, 2021
734805b
add missing eslint plugins and fix its issues
Dec 6, 2021
c3cdbc5
add `isntnt`
Dec 6, 2021
8c8231d
handle any template related errors
Dec 6, 2021
3251112
cleanup pages/main.ts
Dec 6, 2021
8a297b5
move `/static` to `/public/static`
Dec 7, 2021
318789b
add `build:preview`
Dec 7, 2021
1e6162e
configure custom port for `dev` command
Dec 7, 2021
5120d4a
add support for `monck`
Dec 7, 2021
dc58236
bring back `deploy-docs.sh`
Dec 7, 2021
73160c8
replace `classnames` with `clsx`
Dec 7, 2021
a5389a5
add `rsync:*` scripts
Dec 8, 2021
58775f2
adjust README.md
Dec 8, 2021
5eb7d61
setup vuepress 2 with github workflow
Dec 8, 2021
7db5a3f
update docs
Dec 9, 2021
9a823de
update @muban/muban 1.0.0-alpha.28
Dec 13, 2021
b6e3653
add `null-loader` and exclude duplicate `MiniCssExtractPlugin`
Dec 13, 2021
28ecefd
update storybook type imports
Jan 4, 2022
c2cefdd
format style files
Jan 4, 2022
02aa696
cache generated page assets in `MubanPagePlugin` to prevent reprocess…
Jan 4, 2022
180163f
compile mocks through webpack
Jan 4, 2022
f570085
omit page files starting with `_` and rename `main.ts` to `_main.ts`
Jan 5, 2022
747baf6
add support for defining `title`, `meta` and `link` tags within page …
Jan 5, 2022
49bc593
remove `dependencies` as it was causing looping compilations
Jan 5, 2022
b3c02be
use the CLI version of monck to avoid having to create a package for …
Jan 6, 2022
f0af327
set custom cache names for `pages` and `mock` configurations as they …
Jan 6, 2022
cbace32
add `rsync:storybook` script
Jan 6, 2022
e158c4b
add example script on how to run the built site side by side with a s…
Jan 6, 2022
7d22f86
bump @muban/storybook to ^7.0.0-alpha.18
Jan 6, 2022
b76822b
add `CopyEmittedAssetsPlugin` to copy over static assets from the moc…
Jan 6, 2022
6352e49
Update vuepress 2 styling
ThaNarie Jan 6, 2022
30433f1
Update documentation
ThaNarie Jan 6, 2022
2e37fb0
update package name
Jan 7, 2022
3d03bd0
Merge branch 'main' into feature/pota-conversion
Jan 7, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
9 changes: 0 additions & 9 deletions .browserslistrc

This file was deleted.

48 changes: 48 additions & 0 deletions .eslintrc.cjs
@@ -0,0 +1,48 @@
module.exports = {
extends: [
'@mediamonks/eslint-config-base',
'plugin:lit/recommended',
'plugin:lit-a11y/recommended',
],
parserOptions: {
extraFileExtensions: ['.cjs'],
project: './tsconfig.json',
},
rules: {
// Additions
'@typescript-eslint/consistent-type-imports': ['error'],
'@typescript-eslint/naming-convention': [
'error',
{
selector: 'default',
format: ['strictCamelCase'],
leadingUnderscore: 'forbid',
trailingUnderscore: 'forbid',
},
{
selector: 'typeLike',
format: ['StrictPascalCase'],
},
{
selector: 'variable',
// Exception for FunctionComponents
format: ['strictCamelCase', 'StrictPascalCase', 'UPPER_CASE'],
},
{
selector: 'function',
// Exception for FunctionComponents
format: ['strictCamelCase', 'StrictPascalCase'],
},
{
selector: 'enumMember',
format: ['StrictPascalCase'],
},
],
'lit/no-legacy-template-syntax': 'off',
'lit/no-private-properties': 'off',
'lit/no-property-change-update': 'off',
'lit/no-template-map': 'off',
'lit/binding-positions': 'off',
'lit/no-invalid-html': 'off',
},
};
35 changes: 35 additions & 0 deletions .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 }}
2 changes: 2 additions & 0 deletions .gitignore
Expand Up @@ -32,3 +32,5 @@ yarn-error.log*
.eslintcache

/docs/.vuepress/dist
/docs/.vuepress/.cache
/docs/.vuepress/.temp
17 changes: 17 additions & 0 deletions .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
},
];

13 changes: 13 additions & 0 deletions .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
},
];

16 changes: 16 additions & 0 deletions .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"],
};
31 changes: 31 additions & 0 deletions .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);
}
});
}
}
25 changes: 25 additions & 0 deletions .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);
}
);
});
}
}
167 changes: 167 additions & 0 deletions .pota/webpack/plugins/MubanPagePlugin.js
@@ -0,0 +1,167 @@
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) => `<link ${stringifyAttributes(attributes)} />`);
case "meta":
return arr.map((attributes) => `<meta ${stringifyAttributes(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>(.*?)<\/title>/, (match, g0) => match.replace(g0, title));
}

function insertHeadTags(template, headTags) {
const headClosingTagIndex = template.indexOf("</head>");

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.log();
console.error(`Error occurred loading "${file}"`);
console.error(error);

return fallback;
}
}
}