GithubHelp home page GithubHelp logo

natemoo-re / microsite Goto Github PK

View Code? Open in Web Editor NEW
878.0 8.0 16.0 3.32 MB

Do more with less JavaScript. Microsite is a smarter, performance-obsessed static site generator powered by Preact and Snowpack.

Home Page: https://npm.im/microsite

License: MIT License

TypeScript 79.43% JavaScript 14.18% CSS 4.64% AppleScript 1.76%
microsite hydration preact ssg static-site static-site-generator partial-hydration css-modules hydrated-components snowpack

microsite's Introduction



microsite


microsite is a fast, opinionated static-site generator (SSG) built on top of Snowpack. It outputs extremely minimal clientside code using automatic partial hydration.

npm init microsite

Microsite's public API is quite stable, but I caution professional users to consider this a WIP! There are plenty of quirks and bugs (especially with dev mode) that are being ironed out until Microsite reaches a more stable v2.0.0!


Microsite is an ESM node package, so it needs to run in a Node environment which supports ESM. We support the latest version of node v12.x LTS (Erbium) — see Engines for more details.

Ensure that your project includes "type": "module" in package.json, which will allow you to use ESM in your project's node scripts.

Pages

Microsite uses the file-system to generate your static site, meaning each component in src/pages outputs a corresponding HTML file.

Page templates are .js, .jsx, or .tsx files which export a default a Preact component.

Styles

Styles are written using CSS Modules. src/global.css is, as you guessed, a global CSS file injected on every page. Per-page/per-component styles are also inject on the correct pages. They are modules and must be named *.module.css.

Project structure

project/
├── public/             // copied to dist/
├── src/
│   ├── global/
│   │   └── index.css   // included in every generated page
│   │   └── index.ts    // shipped entirely to client, if present
│   ├── pages/          // fs-based routing like Next.js
│   │   └── index.tsx
└── tsconfig.json

Acknowledgments

microsite's People

Contributors

abdusco avatar dependabot[bot] avatar fofyou avatar github-actions[bot] avatar jovidecroock avatar natemoo-re avatar sprabowo avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

microsite's Issues

Make withHydrate a no-op when nested rather than an error

Currently withHydrate detects when it is used on the child element of a hydrated parent and errors. Instead of this, I think it might be better to do nothing (just return the component, don't create the hydration marker). This doesn't change the API, but removes a restriction.

Doing this makes withHydrate more composable. Right now, when creating a child component, you need to worry about whether it's should be the root a hydration tree or not. Ideally, you wouldn't have to do this - you could write components and tag them with withHydrate if they should be hydrated and have the framework automatically determine good hydration roots. I think just ignoring the nested withHydrate achieves this. Children are hydrated as expected (because their parent has a withHydrate) and parents don't have to worry about whether their children are hydrated or not.

Example code (which is not as easily possible today)

components/button.tsx:

const SimpleButton: FunctionalComponent<{classNames: string[]}> = (props) => <button class={props.classNames} onClick={() => alert("Test")}/>Test</button>;
export default withHydrate(SimpleButton);

components/multiple.tsx:

const MultipleButtons = (_: {}) => {
	const [s, setS] = useState(false);
	return <><button onClick={() => setS((s) => !s)}>Change colour</button><SimpleButton classNames={s ? ["red"] : ["blue"]} /></>
}

export default withHydrate(MultipleButtons)

pages/index.tsx:

const Index = (_: {}) => <main><SimpleButton classNames={[]}/><MultipleButtons /></main>

export default definePage(Index);

In this case, SimpleButton is both a root of a hydrated tree (in one context) and a child of a hydrated parent (in a different one). To make this work now, you'd have to export SimpleButton as both a hydrated export and a regular export. If withHydrate ignored children, then only 1 export is enough to make this work.

Doesn't do the thing 🙃

Hi, thought I'd try this

Followed the README, npm init microsite test-microsite then npm run start got the following error:

(node:11635) UnhandledPromiseRejectionWarning: TypeError: (intermediate value) is not iterable
    at dev (file:///home/scott/repos/microsite-test/node_modules/microsite/dist/cli/microsite-dev.js:53:28)
(Use `node --trace-warnings ...` to show where the warning was created)
(node:11635) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). To terminate the node process on unhandled promise rejection, use the CLI flag `--unhandled-rejections=strict` (see https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode). (rejection id: 1)
(node:11635) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.

I'm on Windows WSL fedora using node 14.15.4

thanks

Enhancement: Support SSR-able components as props for hydrated components

I'm a big fan of partial hydration. But, I think the JS bundles can be made smaller. Right now, the code for all children of partially rendered components has to be sent to the client, even if it's not actually required.

Starting example

const Toggle = () => {
	state, setState = useState(false)
	return <button onClick={() => setState((state) => !state)}><FontAwesomeIcon icon={state ? faCalendar : faClock} /></button>
}

In this case FontAwesomeIcon is an example component that needs no interactivity. It is just a helper component to generate the SVG icon given a name.

If it was written like that, hydrating it requires the entire library (in this case react-fontawesome) to be bundled. However, the toggle uses only 2 icons, which could easily be SSR rendered at build time. I don't think this is possible to detect automatically (for the same reason that withHydrate is opt-in), but it should be possible to refactor this to not bundle react-fontawesome.

Vague Implementation

const Toggle = (props: {on: SSRElement, off: SSRElement}) => {
	state, setState = useState(false)
	return <button onClick={() => setState((state) => !state)}>{state ? on : off}</button>

And it's called by passing in the rendered font awesome icons as a prop:

<Toggle on={<SSR><FontAwesomeIcon icon={faCalendar} /></SSR>} off={<SSR><FontAwesomeIcon icon={faClock} /></SSR>} />

where SSR and SSRElement would be provided as utilities by this project.

Details

The tricky bit is the implementation of SSR and SSRElement. A naive one might be for SSR to render its child to an SSRElement (which is a light wrapper around an HTML string) and then for SSRElement to dangerouslysetinnerhtml to the HTML string on the client.

Do you think something like this could work? Would this be an useful feature for this project (considering the focus on minimising runtime javascript)?

Dynamic routes won't work in dev mode

One issue I've run into while using this amazing (really, the idea is awesome!) library is that dynamic routes (e.g src/pages/posts/[id].tsx) won't work for me in dev mode. Maybe I set something up in the wrong way though. I'd appreciate it if you could take a look at whether you can repro.

Steps to reproduce

  1. Clone the repro repository.
  2. Start the site in dev mode npm start
  3. Open http://localhost:8888/posts/1

Expected result

A page opens normally.

Actual result

An error page is shown.

Fix

I quickly looked at the root cause and it seems that the renderPage knows only a path to a dynamic page component e.g. /src/pages/[id].js without knowing a requested URL. My understanding is that it tries to match URLs defined in getStaticPaths with the provided component path which never matches and as a result I'm getting an error page.

If you're able to repro this and the above makes sense to you I can send a fixing PR.

Let me know what you think! Thanks!

Microsite pins a CPU core on creation and building, no output

Hello! Saw your post in the Preact slack, decided to give this a quick test tonight.

Unfortunately I'm having a really weird issue where Microsite appears to pin one (of my 4) CPU cores at 100% when doing any operation. I let npm init microsite microsite-test run for about 15 minutes and all I got were fans. NPX would let me know it installed 4 deps in less than a couple seconds and then I received no more output.

I then decided to skip that and clone your template manually. Same issue. The template is unedited and after running yarn/(npm run) build I get no terminal output and more CPU core maxing out. It does seem to cycle which core gets pinned at 100%, not sure if that's relevant information or not. Might just be Node shuffling them.

I'll provide my system + tool specs here, certainly let me know what else I can provide.

Environment Info:
  System:
    OS: Linux 5.8 Manjaro Linux
    CPU: (4) x64 Intel(R) Core(TM) i7-7700HQ CPU @ 2.80GHz
  Binaries:
    Node: 14.15.0 - /usr/bin/node
    Yarn: 1.22.10 - /usr/bin/yarn
    npm: 6.14.8 - /usr/bin/npm

Adding a Postcss.config.cjs

Hi, when adding a postcss.config.cjs along side with tailwind.config.cjs
I get this error on yarn start

Cannot find module '@snowpack/plugin-postcss'

Preact CDN lookup fails for non-hardcoded submodules

Currently, the Preact CDN transformation only works for preact and preact/hooks. I also see that this is because they are hardcoded as the only things to lookup.

Unfortunately the way the fallback substitution is done breaks other preact submodules (e.g. preact/compat). It tries to load $PINNED_PREACT_URL.js/compat, which is invalid.

My specific issue is with preact/compat, so this could be solved easily just by adding another hardcoded entry to PREACT_CDN_LOOKUP, but it might be worthwhile to not hardcode the import paths and resolve them as they come up.

production build fails when CSS module is imported on multiple pages

Minimal repro

./src/pages/page-a.module.css:

.foo { color: hotpink; }

./src/pages/page-a.tsx:

import { definePage } from 'microsite/page';
import styles from './page-a.module.css';

export default definePage(() => <div class={styles.foo} />);

./src/pages/page-b.tsx (same as page-a.tsx):

import { definePage } from 'microsite/page';
import styles from './page-a.module.css';

export default definePage(() => <div class={styles.foo} />);

Expectation

This should result in a shared CSS asset that's loaded on both page-a.html and page-b.html.

Actual

$ yarn build
[snowpack] ! building source files...
[snowpack] ✔ build complete [0.02s]
[snowpack] ! installing dependencies...
[snowpack] ✔ install complete! [0.07s]
[snowpack] ! verifying build...
[snowpack] ✔ verification complete [0.00s]
[snowpack] ! writing build to disk...
[snowpack] ▶ Build Complete!


Unexpected chunk: page-a.module var modules_72970e0d = {"foo":"foo_951dd"};

export { modules_72970e0d };

node:internal/process/promises:227
          triggerUncaughtException(err, true /* fromPromise */);
          ^

Error [ERR_MODULE_NOT_FOUND]: Cannot find module '~/app/.microsite/ssr/page-a.module.js' imported from ~/app/.microsite/ssr/pages/page-b.js
    at new NodeError (node:internal/errors:329:5)
    at finalizeResolution (node:internal/modules/esm/resolve:301:11)
    at moduleResolve (node:internal/modules/esm/resolve:736:10)
    at Loader.defaultResolve [as _resolve] (node:internal/modules/esm/resolve:847:11)
    at Loader.resolve (node:internal/modules/esm/loader:86:40)
    at Loader.getModuleJob (node:internal/modules/esm/loader:230:28)
    at ModuleWrap.<anonymous> (node:internal/modules/esm/module_job:57:40)
    at link (node:internal/modules/esm/module_job:56:36) {
  code: 'ERR_MODULE_NOT_FOUND'
}

note: this works fine in dev mode

import.meta.env.SSR is true on the client

Consider the following:

console.log("On server:", import.meta.env.SSR)

This logs:

On server: true

both on the client and the server. I expected it to log On server: false for the client.
I might be mistaken on the meaning of the SSR variable however.

[RFC] Built-in Markdown/MDX Support

I've pushed up a WIP Markdown integration under the feat/mdx branch! Give it a spin locally and let me know if you have any feedback.

The package source is under packages/markdown with an example at examples/markdown. It is powered by esbuild via xdm.

You'll notice that it supports both plan .md files as well as .mdx. You may customize the rendering of any element by passing a Preact component for that element's tag name ({ components: { a: (props) => <a {...props} /> } }). Unlike NextJS's built-in markdown support, this integration does not allow Markdown/MDX files to be used as pages/, but I think this is the most flexible choice for the same reasons outlined in next-mdx-remote's README. Unlike next-mdx-remote, hydration is handled entirely by Microsite, so only your components wrapped in withHydrate are rehydrated on the client. I'd be very interested in hearing your thoughts on this integration!

cc @ratorx

microsite crashes on start/build

I've tried to use microsite default example.

❯ MacOS 10.15.7 (19H2)
❯ node v14.15.3
❯ npm 6.14.9
$ npm init microsite microsite-example
$ cd microsite-example
$ npm i
$ npm run start

I got an error:

(node:27049) UnhandledPromiseRejectionWarning: TypeError: (intermediate value) is not iterable
    at dev (/.../node_modules/microsite/dist/cli/microsite-dev.js:53:28)

It was cased by loadConfiguration because it returns non-iterable value. (code)
I fixed it and tried to run it again. Another error appeared :(

UnhandledPromiseRejectionWarning: Error: startDevServer() was been renamed to startServer().
    at startDevServer (/.../node_modules/snowpack/lib/index.js:132227:11)

Okay, tried to use startServer instead of startDevServer. Third attempt :) (code)

[snowpack] ! building dependencies...
[snowpack] node_modules/microsite/dist/client/csr.js
   Module "/web_modules/microsite/_error.js" could not be resolved by Snowpack (Is it installed?).
(node:27826) UnhandledPromiseRejectionWarning: Error: Install failed.
    at Object.install (/.../node_modules/snowpack/lib/index.js:47788:23)

I stopped here :) I could continue to investigate it deeper. But I would like to ask If I'm doing smth wrong?

Yarn add microsite in existing project

I have an existing project using https://www.npmjs.com/package/@jaredpalmer/after and I wanted to give microsite a go to see if I could get the site part of my app running with microsite and lighten the load.

I followed the manual setup instructions:

yarn add microsite preact
npx microsite dev

I get this error:

$ microsite dev
(node:64666) UnhandledPromiseRejectionWarning: Error: Cannot find module '/Users/munter/git/heap.web.afterjs/node_modules/cheerio-select/lib/index.js'. Please verify that the package.json has a valid "main" entry
    at tryPackage (internal/modules/cjs/loader.js:320:19)
    at Function.Module._findPath (internal/modules/cjs/loader.js:533:18)
    at Function.Module._resolveFilename (internal/modules/cjs/loader.js:879:27)
    at Function.Module._load (internal/modules/cjs/loader.js:742:27)
    at Module.require (internal/modules/cjs/loader.js:964:19)
    at require (internal/modules/cjs/helpers.js:88:18)
    at Object.<anonymous> (/Users/munter/git/heap.web.afterjs/node_modules/cheerio/lib/static.js:11:14)
    at Module._compile (internal/modules/cjs/loader.js:1075:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:1096:10)
    at Module.load (internal/modules/cjs/loader.js:940:32)
    at Function.Module._load (internal/modules/cjs/loader.js:781:14)
    at Module.require (internal/modules/cjs/loader.js:964:19)
    at require (internal/modules/cjs/helpers.js:88:18)
    at Object.<anonymous> (/Users/munter/git/heap.web.afterjs/node_modules/cheerio/index.js:8:21)
    at Module._compile (internal/modules/cjs/loader.js:1075:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:1096:10)
(Use `node --trace-warnings ...` to show where the warning was created)
(node:64666) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). To terminate the node process on unhandled promise rejection, use the CLI flag `--unhandled-rejections=strict` (see https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode). (rejection id: 1)
(node:64666) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
(node:64666) UnhandledPromiseRejectionWarning: Error: Cannot find module '/Users/munter/git/heap.web.afterjs/node_modules/cheerio-select/lib/index.js'. Please verify that the package.json has a valid "main" entry
    at tryPackage (internal/modules/cjs/loader.js:320:19)
    at Function.Module._findPath (internal/modules/cjs/loader.js:533:18)
    at Function.Module._resolveFilename (internal/modules/cjs/loader.js:879:27)
    at Function.Module._load (internal/modules/cjs/loader.js:742:27)
    at Module.require (internal/modules/cjs/loader.js:964:19)
    at require (internal/modules/cjs/helpers.js:88:18)
    at Object.<anonymous> (/Users/munter/git/heap.web.afterjs/node_modules/cheerio/lib/static.js:11:14)
    at Module._compile (internal/modules/cjs/loader.js:1075:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:1096:10)
    at Module.load (internal/modules/cjs/loader.js:940:32)
    at Function.Module._load (internal/modules/cjs/loader.js:781:14)
    at Module.require (internal/modules/cjs/loader.js:964:19)
    at require (internal/modules/cjs/helpers.js:88:18)
    at Object.<anonymous> (/Users/munter/git/heap.web.afterjs/node_modules/cheerio/index.js:8:21)
    at Module._compile (internal/modules/cjs/loader.js:1075:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:1096:10)
(node:64666) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). To terminate the node process on unhandled promise rejection, use the CLI flag `--unhandled-rejections=strict` (see https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode). (rejection id: 2)
munter at peters-mbp in ~/git/heap.web.afterjs on microsite*
$ npx microsite dev
(node:66431) UnhandledPromiseRejectionWarning: Error: Cannot find module '/Users/munter/git/heap.web.afterjs/node_modules/cheerio-select/lib/index.js'. Please verify that the package.json has a valid "main" entry
    at tryPackage (internal/modules/cjs/loader.js:320:19)
    at Function.Module._findPath (internal/modules/cjs/loader.js:533:18)
    at Function.Module._resolveFilename (internal/modules/cjs/loader.js:879:27)
    at Function.Module._load (internal/modules/cjs/loader.js:742:27)
    at Module.require (internal/modules/cjs/loader.js:964:19)
    at require (internal/modules/cjs/helpers.js:88:18)
    at Object.<anonymous> (/Users/munter/git/heap.web.afterjs/node_modules/cheerio/lib/static.js:11:14)
    at Module._compile (internal/modules/cjs/loader.js:1075:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:1096:10)
    at Module.load (internal/modules/cjs/loader.js:940:32)
    at Function.Module._load (internal/modules/cjs/loader.js:781:14)
    at Module.require (internal/modules/cjs/loader.js:964:19)
    at require (internal/modules/cjs/helpers.js:88:18)
    at Object.<anonymous> (/Users/munter/git/heap.web.afterjs/node_modules/cheerio/index.js:8:21)
    at Module._compile (internal/modules/cjs/loader.js:1075:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:1096:10)
(Use `node --trace-warnings ...` to show where the warning was created)
(node:66431) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). To terminate the node process on unhandled promise rejection, use the CLI flag `--unhandled-rejections=strict` (see https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode). (rejection id: 1)
(node:66431) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
(node:66431) UnhandledPromiseRejectionWarning: Error: Cannot find module '/Users/munter/git/heap.web.afterjs/node_modules/cheerio-select/lib/index.js'. Please verify that the package.json has a valid "main" entry
    at tryPackage (internal/modules/cjs/loader.js:320:19)
    at Function.Module._findPath (internal/modules/cjs/loader.js:533:18)
    at Function.Module._resolveFilename (internal/modules/cjs/loader.js:879:27)
    at Function.Module._load (internal/modules/cjs/loader.js:742:27)
    at Module.require (internal/modules/cjs/loader.js:964:19)
    at require (internal/modules/cjs/helpers.js:88:18)
    at Object.<anonymous> (/Users/munter/git/heap.web.afterjs/node_modules/cheerio/lib/static.js:11:14)
    at Module._compile (internal/modules/cjs/loader.js:1075:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:1096:10)
    at Module.load (internal/modules/cjs/loader.js:940:32)
    at Function.Module._load (internal/modules/cjs/loader.js:781:14)
    at Module.require (internal/modules/cjs/loader.js:964:19)
    at require (internal/modules/cjs/helpers.js:88:18)
    at Object.<anonymous> (/Users/munter/git/heap.web.afterjs/node_modules/cheerio/index.js:8:21)
    at Module._compile (internal/modules/cjs/loader.js:1075:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:1096:10)
(node:66431) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). To terminate the node process on unhandled promise rejection, use the CLI flag `--unhandled-rejections=strict` (see https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode). (rejection id: 2)

Make Preact on skypack optional?

Would you consider making the usage of Skypack CDN URLs for Preact optional? This could potentially be accomplished by removing the mapping logic and deferring to the built in behavior introduced in Snowpack 3.0.

My motivation is that using CDNs for external dependencies is actually increasingly a performance negative, since most browsers now implement cache partitioning.

I spent some time trying to make this change on my fork but it turned out to be fairly involved, and I wanted to check in about whether it would be a welcome change before I get deeper into it.

React or Preact?

I need to make a decision here. It currently ships with @types/react and does some gnarly transforms to the bundle to swap in preact in a non-ideal way.

The options are:

  • Use react typings and swap in preact/compat at compile time?
  • Drop react and just use bare preact

Provide a public document context for SSG...

...or Page, or Main context, to avoid ambiguity.

~~~Context~~~ (ahem) use case

I’m currently trying to provide context to certain deep nodes which I expect to use on most/all pages, and which require an async initialization. (Specifically, to give credit to another couple awesome projects: Shiki and remark-shiki-twoslash.)

I’m also trying to use inline markdown with a template literal because I’m a weird person who wants the inverse of MDX: I want markdown in JSX, not the other way around, because it gives me all the cool Remark/Rehype stuff but with all the cool tooling that comes relatively for free in a .tsx file. Plus I’m kind of obsessive about collocation of component definitions which is why I put all that effort into getting Fela working.

I’ve tried setting up in a custom document or even in a page’s getStaticProps, then providing the (now synchronous) highlighter instance with a context provider, but that appears to be optimized out at build time. I’d essentially have to hydrate the entire page to keep it, or move a lot of boilerplate down through props.

Caveats filing the issue

  • As always I’m doing weird custom stuff so it’s entirely possible I’m fighting my own setup/not using tooling properly. I ran out of steam debugging and felt like it’s more productive to start a discussion.

  • This could potentially be addressed by #99 in ways I’ll handwave about to avoid derailing my own issue; I think this hypothetical API could be valuable and a better DX than yet another build plugin.

Proposal

I know there’s an internal doc context used at the SSG stage to provide various details for the final full HTML render, much like the internal head context.

I don’t want to overload this for end users, but provide a distinct (opt in of course) API for sites to define their own document/page context that’s guaranteed to be available during build/stripped out unless explicitly hydrated.

In fact I might even want to go one step further and suggest a general API/DX improvement over the Next/Gatsby style interface (which should be backwards compatible unless I’ve failed to think of something), and say that props supplied in a custom document or getStaticProps could automatically provide context consumable by deeper components. This would address very similar issues I had with those doc/page APIs that don’t allow per-component data fetching at build time.

Addressing problems and risks I can anticipate

  • More API, more to document, more to maintain and risk breakage; but this kind of API is especially useful and viable for a static-first library.

  • I’m not especially fond of context (or hooks) because they make reasoning about components harder; for the DX it’s a huge win, allowing more build-time work in the normal flow without more tooling.

  • The biggest risk I see is accidental deoptimization by making build-time stuff part of the bundle; there’s already safeguards in place distinguishing build/bundle and this could follow suit.

  • Type safety is a major concern for me, and anything automated/implicit could lead to magic and assumptions; definitely an argument against automating any of this, probably easier to target doc-global and/or a very specific API for context overall. Or a useDocument/usePage hook but I’m losing it even suggesting that 🙃

  • I already said the context API isn’t my favorite, is it actually right for this? I’m just trying to use prior art. Open question IMO.

Package size is quite big

The package install size 160 MB. Reducing the size will help install speed and save some bandwidth.

Perhaps there are lighter alternatives that can achieve the same, or maybe some dependencies are only development dependencies?

The biggest packages when I install microsite locally on Linux:
image

Error when trying to run project

My steps:

  • npm init microsite microsite
  • cd /microsite
  • yarn install
  • yarn start

➜ microsite yarn start
yarn run v1.22.10
warning ../../package.json: No license field
$ microsite
file:///##/##/##/microsite/node_modules/microsite/dist/cli/microsite-dev.js:53
const [errs, config] = await loadConfiguration('dev');
^

TypeError: (intermediate value) is not iterable
at dev (file:///##/##/##/microsite/node_modules/microsite/dist/cli/microsite-dev.js:53:28)
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.

Vendor js may be bundled/loaded with no hydration on page

I’m not entirely convinced this is a bug or that it even needs to be addressed, but I wanted to report what I’ve seen and hopefully circle back with a minimal repro as I’m able.

Some (as yet unclear) combination of dependencies and imports has led some pages on my site to bundle and load vendor/Preact/microsite-runtime etc even without any hydration. I had to bail on investigation for now because I’m trying not to rabbit hole and I worked around it with Snowpack config voodoo. But I did want to make sure it’s noted here so I can come back and take a look later.

If I get a few spare brain cycles I’ll try a binary search on the set of Snowpack config changes that worked around it to hopefully isolate the cause. I strongly suspect given the state of my project that #105 is the root cause. My reasoning here is that I currently have several pages that all have the same set of imports (and display this behavior), and one page that doesn’t (and behaves as expected).

And just so I don’t lose my own partial investigation brain context the place I was looking into addressing this was here, looking more at info.importers. But it might be simpler to just look at the final result of hydration to determine whether the vendor import is needed.

Watch/Serve mode

microsite build is built on top of rollup, but does multiple passes for the build stage. I'd like to reduce the number of passes as much as possible, which will improve perf and make the logic required for watching a lot simpler.

The API will be exposed as microsite dev and microsite build --serve

Preact import regex matches too much

The preact import regex here matches too much. I’m guessing the desired behaviour is the match preact imports. However it also matches other imports that happen to contain preact in them (e.g. import {MDXProvider} from “@mdx-js/preact”, which is a completely different module that happens to contain preact in the name)

I think it’s a fairly easy fix? I think that just adding a single to the regex before preact would avoid this? I’m unsure since this is the first time I’m using preact, so I don’t know exactly what import names the project uses. Assuming it’s just “preact”, this should fix it.

I think a bit of debug info around the transforms in general would be quite helpful. This was fairly annoying to debug because it ended up causing an import error after all the imports from the 2 separate modules were joined into 1 statement import {h, ..., MDXProvider} from “preact”, but I’m not sure how you could achieve this, so I’ll just leave the idea here.

Hydrated component isn't initialized in prod builds when it is exported/imported under a name different from the name of the component it decorates

A bit cryptic title but let me explain what this is about :)

Currently, in production mode code assumes that the name of the component that the withHydrate hook decorates and the name under which the decorated component is imported into the page are the same which is not always true.

The withHydrate hook when renders HTML stores the name of the component it decorates as a marker. The hydration code then reads the marker and tries to locate a component with this name among hydration bindings. Hydration bindings are determined based on named exports of the component file or default import names in the page component.

There are two cases when this logic fails.

A named export case:

// hydrated-component.tsx
export const HydratedComponent = withHydrate(HydratedComponentInternal);

// page.tsx
import {HydratedComponent} from './hydrated-component.tsx';

Default import case:

// hydrated-component.tsx
export default withHydrate(HydratedComponentInternal);

// page.tsx
import HydratedComponent from './hydrated-component.tsx';

In both cases in prod, the hydration code would try to locate the HydratedComponentInternal in the binding but this attempt would fail as there would be only single binding for the HydratedComponent.

Fixing this turned out to be tricky. The only fix I can think about is to parse hydration chunks, determine exported component names, and prefill a displayName parameter of the withHydrate hook with the corresponding names. This solution feels a bit hacky.

I'm wondering if you have any thoughts on what would be the right way to fix it. Take a look when you have time =)

Adding a repro below.

Steps to reproduce

  1. Checkout a named-export-fix branch in this repository.
  2. Run npm run serve
  3. Open http://localhost:8888/

Expected behavior

The page renders, HydratedComponentA and HydratedComponentB get hydrated and two alert messages are shown.

Actual behavior

The page renders but no components are hydrated because of a runtime js error.

dev server fails to load fetch

Using fetch function inside getStaticProps works fine running build but fails in dev server start.

yarn start
...
[microsite] ✔ Microsite started on http://localhost:8888

ReferenceError: fetch is not defined
    at getStaticProps (/src/pages/blog.js:14:17)
export default definePage(BlogPage, {
    path: '/blog',
    async getStaticProps(context) {
        const res = await fetch(`https://url`);
        const blogs = await res.json()
        return {
          props: {blogs}
        };
    },
});

Is there something simple i'm missing that is preventing the dev server from accessing fetch function?
I'm very new to snowpack so may have missed some thing that is assumed.

OS: macos mojave
Node version: v14.13.0

Potentially a similar issue to #132

Thanks!

Investigate inconsistent hydration results

  • hydrating on idle consistently caused the component to mount as expected and then immediately unmount (which I see at least on mobile for either hydration case on your example)

  • hydrating on visible caused hydration to sometimes not occur at all/immediately unmount, sometimes occur adjacent to the server rendered node, and sometimes function normally

I strongly suspect there’s some timing factor involved here. But I was in build mode so I didn’t get hung up on it once I brute forced a fix

Originally posted by @eyelidlessness in #107 (comment)

getStaticProps + node builtins + dev server fails

I've been trying to statically generate pages with getStaticProps and getStaticPaths, but the dev server crashes whenever I try to import fs; production builds work just fine.

 ❱ yarn start
yarn run v1.22.10
warning package.json: No license field
$ microsite
[snowpack] ! building dependencies...
~/snowpack/esinstall/lib/entrypoints.js:168
        throw new Error(`Package "${dep}" not found. Have you installed it? ${depManifestLoc ? depManifestLoc : ''}`);
              ^

Error: Package "fs" not found. Have you installed it?
    at Object.resolveEntrypoint (~/snowpack/esinstall/lib/entrypoints.js:168:15)
    at resolveWebDependency (~/snowpack/esinstall/lib/index.js:89:31)
    at Object.install (~/snowpack/esinstall/lib/index.js:203:36)
    at Object.run (~/snowpack/snowpack/lib/sources/local-install.js:43:43)
    at installDependencies (~/snowpack/snowpack/lib/sources/local.js:54:49)
    at async Object.prepare (~/snowpack/snowpack/lib/sources/local.js:109:35)
    at async startServer (~/snowpack/snowpack/lib/commands/dev.js:278:27)
    at async dev (file://~/microsite/examples/root/node_modules/microsite/dist/cli/microsite-dev.js:57:22)

I've tested this with the latest SHA of both [email protected] and snowpack, and get the same result. You should be able to repro by running npm start in this example project: ./examples/root

microsite router

Hi,

Firstly good stuff for this alternative to Next...

I've one suggestion for the next release it should be cool to add built-in Router like next/router ?
Like gettings route params in pages or components with useRouter
It's in the plans ?

Regards,

Vincent

How to integrate PostCSS

Hello, I'm trying to enable PostCSS but the configuration file (postcss.config.js) inside the project root is not recognized.

I'm also trying to integrate PostCSS by using the official snowpack plugin, but it doesn't seem to work. I've created both snowpack.config.json and postcss.config.js.

EDIT: It seems that only .postcssrc file is recognised.

Build fails with "Error: You must supply options.input to rollup"

I just discovered microsite and wanted to try it out, but I get this error:

> yarn create microsite microsite-test
> cd microsite-test
> yarn
> yarn build
yarn run v1.22.10
$ microsite build
[17:03:44] [snowpack] Hint: run "snowpack init" to create a project config file. Using defaults...
[17:03:44] [snowpack] ! building files...
[17:03:44] [snowpack] ✔ files built. [0.06s]
[17:03:44] [snowpack] ! building dependencies...
[17:03:45] [snowpack] ✔ dependencies built. [0.29s]
[17:03:45] [snowpack] ! writing to disk...
[17:03:45] [snowpack] ✔ write complete. [0.01s]
[17:03:45] [snowpack] ▶ Build Complete!
(node:9756) UnhandledPromiseRejectionWarning: Error: You must supply options.input to rollup
    at Graph.generateModuleGraph (file:///C:/Users/malte/Programme/microsite-test/node_modules/rollup/dist/es/shared/rollup.js:19253:19)
    at processTicksAndRejections (internal/process/task_queues.js:93:5)
    at async Graph.build (file:///.../microsite-test/node_modules/rollup/dist/es/shared/rollup.js:19194:9)
    at async rollupInternal (file:///.../microsite-test/node_modules/rollup/dist/es/shared/rollup.js:20165:9)
    at async bundlePagesForSSR (file:///.../microsite-test/node_modules/microsite/dist/cli/microsite-build.js:144:20)
    at async Promise.all (index 0)
    at async build (file:///.../microsite-test/node_modules/microsite/dist/cli/microsite-build.js:51:33)
(Use `node --trace-warnings ...` to show where the warning was created)
(node:9756) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). To terminate the node process on unhandled promise rejection, use the CLI flag `--unhandled-rejections=strict` (see https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode). (rejection id: 2)
(node:9756) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
Done in 1.70s.

Am I doing something wrong here or is this a bug?

OS: Windows 10
Node version: v14.16.0
I get the same error when using npm instead of yarn

Consider using user-supplied snowpack config for static data extraction

This is more of a bug report than a request (like #91), but I know you’re still thinking on how to expose config. That said, I took some time to explore integration with Linaria and eventually gave up because the “special sauce” depends on runtime calls into pages, but Linaria compiles those out at build time (it errors if you miss the compile step).

In terms of how it’s a bug report, what this means is that builds simultaneously succeed (with custom config) and fail (pulling static props/paths) because the failure branch doesn’t apply any snowpack configuration to its runtime.

What this would probably look like is using a unified (or separate if you or other users would prefer) snowpack context for both the main build and the static props/paths extraction. I’m not sure how involved this would be, but I’d be happy to fork and give it a shot if you’d be interested in the contribution.

On that note, I’ve spent ... well, more time than I’d like to admit wading through the microsite internals trying to get my own setup working, and I’m very impressed with the foundation and would love to contribute in general. There’s some places I’ve had a few head scratches, mostly where some stuff in the interface is different at dev/build time or where configs for each vary significantly, but I think I have most if not all of the project in brain memory at this point and if you want another brain on this I’d love to get involved.

Resolution fails for index files

Given src/components/Test/index.tsx, an import from ./components/Test fails because it resolves to ./components/Test.js (which doesn't exist)

Module interop compatibility issue on Node 12.19

When running the microsite CLI on Node 12.19.1, the following error is thrown:

(node:53186) ExperimentalWarning: The ESM module loader is experimental.
(node:53186) UnhandledPromiseRejectionWarning: file:///Users/tyler/dev/_temp/microsite-test/node_modules/microsite/dist/utils/command.js:6
import { createConfiguration } from "snowpack";
         ^^^^^^^^^^^^^^^^^^^
SyntaxError: The requested module 'snowpack' is expected to be of type CommonJS, which does not support named exports. CommonJS modules can be imported by importing the default export.
For example:
import pkg from 'snowpack';
const { createConfiguration } = pkg;
    at ModuleJob._instantiate (internal/modules/esm/module_job.js:97:21)
    at async ModuleJob.run (internal/modules/esm/module_job.js:143:20)
    at async Loader.import (internal/modules/esm/loader.js:182:24)
(node:53186) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). To terminate the node process on unhandled promise rejection, use the CLI flag `--unhandled-rejections=strict` (see https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode). (rejection id: 1)
(node:53186) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.

Steps to reproduce:

npm init microsite some-dir

cd some-dir

npm i

npm start

Known workarounds

  • Upgrade Node 12 to latest (12.20.1 at the time of writing)
  • Upgrade Node to LTS (14.15.4 at the time of writing)

Other details

This error occurs for microsite 1.0.3 as well as 1.1.0-next.2.

Missing typescript installation instructions

I tried installing microsite into an existing project that already has a tsconfig.json. I used the manual setup instructions.

npm i microsite preact
npx microsite dev

I get this error:

$ microsite dev
(node:61620) UnhandledPromiseRejectionWarning: TypeError [ERR_INVALID_ARG_TYPE]: The "path" argument must be of type string. Received undefined
    at validateString (internal/validators.js:122:11)
    at resolve (path.js:980:7)
    at resolveTsconfigPathsToAlias (file:///Users/munter/git/heap.web.afterjs/node_modules/microsite/dist/utils/command.js:61:15)
    at loadConfiguration (file:///Users/munter/git/heap.web.afterjs/node_modules/microsite/dist/utils/command.js:28:11)
    at async dev (file:///Users/munter/git/heap.web.afterjs/node_modules/microsite/dist/cli/microsite-dev.js:53:28)
(Use `node --trace-warnings ...` to show where the warning was created)
(node:61620) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). To terminate the node process on unhandled promise rejection, use the CLI flag `--unhandled-rejections=strict` (see https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode). (rejection id: 1)
(node:61620) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.

It seems that resolveTsconfigPathsToAlias depends on tsconfig.json having configured baseUrl and paths.

The manual installation instructions do not mention this. And the typescript docs say Microsite automatically supports the tsconfig.json "paths" and "baseUrl" options , which I read as them not being required.

Any chance you can push a release of the current state of `next`?

I’m currently working on a project setup with the 1.1.0-next.2 branch (for snowpack config support), but I’m running into some of the bugs you appear to have fixed in later commits (particularly base path and global/shared hydration emit). I tried using a git commit reference in my package but this being a monorepo it’s not that simple. I’ve also tried aliasing to workaround, but I’m ending up reimplementing a lot of the lib in the process.

As a thank you (just in general even if you’re not ready to push a release), I’m pretty close to a CSS-in-JS solution with Fela, including static CSS extraction, which I hope to offer as an example/plugin or even a PR into main if it’s something you’d want.

Thanks so much, I’m really looking forward to developing with microsite.

Using PostCSS with @snowpack/plugin-build-script plugin logs error

When using the @snowpack/plugin-build-script plugin combined with the PostCSS CLI microsite dev stops and logs:

It looks like you're trying to use PostCSS!
Microsite will automatically use your configuration, but requires some 'devDependencies' to do so.

Please run 'npm install --save-dev @snowpack/plugin-postcss'

Relevant config/code:

snowpack.config.cjs

module.exports = {
  'plugins': [
    [
      '@snowpack/plugin-build-script',
      {'cmd': 'postcss', 'input': ['.css'], 'output': ['.css']},
    ],
  ],
};

package.json snippet:

"devDependencies": {
  "@snowpack/plugin-build-script": "^2.1.0",
  "postcss": "^8.2.5",
  "postcss-cli": "^8.3.1"
},
"postcss": {
  "plugins": {
    "tailwindcss": {}
  }
}

Wondering if there is a way to use the @snowpack/plugin-build-script plugin instead of using @snowpack/plugin-postcss, I really like the simplicity of the build script plugin more than a specific plugin for PostCSS if possible.

Hydration fails when a component is used on multiple pages

Version: 1.1.0-next.3

Steps to reproduce

  1. Render a hydrated component on multiple pages
  2. Build/serve
  3. Load in browser

Expected behavior

The component will hydrate on each page.

Observed behavior

No JS is loaded at all.

Apparent cause

Any shared component with hydration is bundled as a shared chunk, but the shared chunk is excluded from hydrateBindings, and doesn’t appear to be referenced anywhere else. If I comment out that check it does hydrate as expected.

I was tempted to offer a PR removing that check, but I can’t shake the feeling that there’s a reason it’s there and the intent was to handle that case differently.


Related side note: in terms of contribution, my usual approach is to write a failing test exercising my bug, then fix to make the test pass. But at present Microsite doesn’t appear to have a test suite. Is that something you’d be interested in? If so do you have a preferred test framework? I’d be happy to look into setting up CI for any tests I contribute, and to try to increase coverage as I contribute.

getStaticPaths uses module’s path when returning params object

Steps to reproduce

  1. Install microsite@next
  2. Create a page posts.tsx
  3. Follow these instructions from the docs
  4. Return { paths: [ { params: { id: '1' } }, { params: { id: '2' } } ] }
  5. Build

Expected result

Two pages will be built, with their respective content:

  • /posts/1.html
  • /posts/2.html

Observed result

Both pages will be built at posts.html, with whichever finished last winning.

Notes and workaround

  • The non-params string version worked as expected for me.
  • The path template static typing is cool, and it’s awesome to see that TS feature used in the wild! However, it loses inference on any other returned params.
  • I’m happy to take a look at both issues if I’m able to come up for air this weekend 🙂

Hydration fails with nested props arrays/objects

Props which reproduce this:

{
  "autoplay": true,
  "playsinline": true,
  "sources": [
    {
      "src": "/home/background-desktop.webm",
      "type": "video/webm"
    },
    {
      "src": "/home/background-desktop.mp4",
      "type": "video/mp4"
    }
  ]
}

These props produce this error:

Uncaught SyntaxError: Unexpected end of JSON input
    at JSON.parse (<anonymous>)
    at p (microsite.js:formatted:93)
    at v (microsite.js:formatted:112)
    at t (microsite.js:formatted:124)

I suspect this would be solved by changing this regex to:

const ATTR_REGEX = /(:?\w+)=(\S+|[{[].*?[}\]])/g;

But I'm hesitant to open a PR because I don't fully grasp what's expected in the hydration markers. That said, I'd also probably recommend not using a regex for this purpose in the first place. A few options I've thought of:

  • Use a special token indicating the beginning/end of a given JSON chunk
  • Separate the markers from the props so they can be treated as plain JSON
  • Use a safer serialization format (I hesitate to mention base 64 because it increases the size, but you get the idea)

Slow builds

Since rollup is executed in a few phases, builds are pretty slow.

I'd like to switch to esbuild as soon as support for CSS Modules lands.

Omit base tag by default?

The current default for base is /, which is also the browser default. But including it can have some unexpected side effects when a base tag is included explicitly:

  • Any bare #hash references in links will be relative to / rather than the active page (which can be worked around by prefixing all of them with the relative/absolute path, but that can be pretty redundant and kind of a DX pain).

  • Some older browsers apply these same rules to url references in SVG/CSS, while most newer browsers don’t, which makes behavior unpredictable and error prone, and may increase testing surface area depending on targets.

In my opinion the base tag is generally more trouble than it’s worth, though I definitely see it used well in the examples and can imagine large multi-build projects benefiting from it. I think a reasonable balance would be to omit it and make the core relative paths absolute by default, but accommodate it when builds want/need a nested base.

Risks

I don’t know if Snowpack/Rollup internals make other chunk paths relative by default but if they do it would probably be a heavy lift to accommodate this.

This would be a trivial change otherwise and I’d be happy to offer a PR tomorrow if it’s a welcome change.

Bundle/treeshake sourcecode with Rollup

As a follow-up to #81, I'd like to swap tsc out for a custom rollup build. Inlining dependencies should reduce the install footprint significantly and make everything a lot faster.

If possible, the dependencies should only include snowpack, preact, and the necessary @snowpack/plugin- packages. Everything else should be moved to devDependencies.

Consider other JSX compilers

This is a significant stretch and I know it is, but I’m floating it as a possibility for an even more smol client side runtime. I have no idea how complex this would be. But I’ll state my case:

JSX is better than any particular JSX library

JSX is entirely a dev experience fiction (much like CSS-in-JS can be). It’s a declarative data structure DSL that doesn’t necessarily correspond to any particular implementation. It’s effectively a macro where you inject the macro implementation at build time.

This allows compilation to target arbitrary renderers and arbitrary reconciliation engines. Preact is awesome, but it’s explicitly tied to HTML/DOM (invalidating arbitrary renderers), and other optimizing compilers can challenge its size and performance even in that space.

It’s worth noting that I bounced off investigating Solid in favor of Microsite for my current project specifically because Microsite has first-class support for partial hydration but Solid currently has it as an open issue. I’ll also note @ryansolid works on the team developing Marko, which notably has truly automatic partial hydration (compiler optimized, not dev), but notably doesn’t support JSX. I feel like the potential for these two projects to meld is great for those of us developing mostly-static sites who want a JSX dev experience.

The challenge is that some of these implementations are ... esoteric and have their own limitations. Moreover, Microsite explicitly references/configures/wraps Preact with its own semantics in mind.

A potential mitigation of that risk is another promising project, JSX Lite which compiles a common JSX spec to a variety of backends (including Solid).

I have no idea if something like this is desirable for this project but I wanted to float the idea and see if you have interest. As I mentioned in #92 this would be something I’d be happy to work on.

(Also since I’ve @‘ed Ryan I’m super curious to hear his input on this if he’d like to join.)

Deployment guides?

  • Create optimized config for Vercel's frameworks
  • Create optimized config for Netlify
  • Add documentation for Vercel and Netlify deployments

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.