Repository: FredKSchott/snowpack Branch: main Commit: 7f9b5455683a Files: 903 Total size: 4.6 MB Directory structure: gitextract_28t6qide/ ├── .changeset/ │ ├── README.md │ └── config.json ├── .editorconfig ├── .github/ │ ├── CODEOWNERS │ ├── ISSUE_TEMPLATE/ │ │ ├── ---bug-report.yml │ │ ├── ---feature-request.yml │ │ └── config.yml │ ├── ISSUE_TEMPLATE.md │ ├── PULL_REQUEST_TEMPLATE.md │ └── workflows/ │ ├── format.yml │ ├── lint.yml │ ├── release.yml │ └── test.yml ├── .gitignore ├── .prettierignore ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── create-snowpack-app/ │ ├── README.md │ ├── app-scripts-lit-element/ │ │ ├── package.json │ │ ├── snowpack.config.js │ │ └── tsconfig.base.json │ ├── app-scripts-preact/ │ │ ├── babel.config.json │ │ ├── jest/ │ │ │ ├── babelTransform.js │ │ │ ├── cssTransform.js │ │ │ └── fileTransform.js │ │ ├── jest.config.js │ │ ├── package.json │ │ ├── snowpack.config.js │ │ └── tsconfig.base.json │ ├── app-scripts-react/ │ │ ├── babel.config.json │ │ ├── jest/ │ │ │ ├── babelTransform.js │ │ │ ├── cssTransform.js │ │ │ ├── esbuildTransform.js │ │ │ ├── fileTransform.js │ │ │ └── importMetaBabelPlugin.js │ │ ├── jest.config.js │ │ ├── package.json │ │ ├── snowpack.config.js │ │ └── tsconfig.base.json │ ├── app-scripts-svelte/ │ │ ├── jest/ │ │ │ ├── babelTransform.js │ │ │ └── importMetaBabelPlugin.js │ │ ├── jest.config.js │ │ ├── package.json │ │ └── snowpack.config.js │ ├── app-scripts-vue/ │ │ ├── package.json │ │ ├── snowpack.config.js │ │ └── tsconfig.base.json │ ├── app-template-11ty/ │ │ ├── .eleventy.js │ │ ├── .npmignore │ │ ├── .prettierrc │ │ ├── README.md │ │ ├── _includes/ │ │ │ └── layouts/ │ │ │ └── base.njk │ │ ├── _output/ │ │ │ ├── about/ │ │ │ │ └── index.html │ │ │ ├── index.html │ │ │ └── static/ │ │ │ └── index.css │ │ ├── _template/ │ │ │ ├── about.md │ │ │ ├── index.njk │ │ │ └── static/ │ │ │ ├── index.css │ │ │ └── robots.txt │ │ ├── package.json │ │ ├── snowpack.config.mjs │ │ └── src/ │ │ └── index.js │ ├── app-template-blank/ │ │ ├── .npmignore │ │ ├── .prettierrc │ │ ├── README.md │ │ ├── package.json │ │ ├── public/ │ │ │ ├── index.html │ │ │ └── robots.txt │ │ ├── snowpack.config.mjs │ │ └── src/ │ │ ├── index.css │ │ └── index.js │ ├── app-template-blank-typescript/ │ │ ├── .npmignore │ │ ├── .prettierrc │ │ ├── README.md │ │ ├── package.json │ │ ├── public/ │ │ │ ├── index.html │ │ │ └── robots.txt │ │ ├── snowpack.config.mjs │ │ ├── src/ │ │ │ ├── index.css │ │ │ └── index.ts │ │ ├── tsconfig.json │ │ └── types/ │ │ └── static.d.ts │ ├── app-template-lit-element/ │ │ ├── .npmignore │ │ ├── .prettierrc │ │ ├── README.md │ │ ├── babel.config.json │ │ ├── package.json │ │ ├── public/ │ │ │ ├── index.css │ │ │ ├── index.html │ │ │ └── robots.txt │ │ ├── snowpack.config.mjs │ │ └── src/ │ │ ├── app-root.js │ │ └── index.js │ ├── app-template-lit-element-typescript/ │ │ ├── .npmignore │ │ ├── .prettierrc │ │ ├── README.md │ │ ├── babel.config.json │ │ ├── package.json │ │ ├── public/ │ │ │ ├── index.css │ │ │ ├── index.html │ │ │ └── robots.txt │ │ ├── snowpack.config.mjs │ │ ├── src/ │ │ │ ├── app-root.ts │ │ │ └── index.ts │ │ ├── tsconfig.json │ │ └── types/ │ │ └── static.d.ts │ ├── app-template-minimal/ │ │ ├── README.md │ │ ├── index.css │ │ ├── index.html │ │ ├── index.js │ │ ├── package.json │ │ └── snowpack.config.mjs │ ├── app-template-preact/ │ │ ├── .npmignore │ │ ├── .prettierrc │ │ ├── README.md │ │ ├── package.json │ │ ├── public/ │ │ │ ├── index.html │ │ │ └── robots.txt │ │ ├── snowpack.config.mjs │ │ ├── src/ │ │ │ ├── App.css │ │ │ ├── App.jsx │ │ │ ├── App.test.jsx │ │ │ ├── index.css │ │ │ └── index.jsx │ │ └── web-test-runner.config.js │ ├── app-template-preact-typescript/ │ │ ├── .npmignore │ │ ├── .prettierrc │ │ ├── README.md │ │ ├── package.json │ │ ├── public/ │ │ │ ├── index.html │ │ │ └── robots.txt │ │ ├── snowpack.config.mjs │ │ ├── src/ │ │ │ ├── App.css │ │ │ ├── App.test.tsx │ │ │ ├── App.tsx │ │ │ ├── index.css │ │ │ └── index.tsx │ │ ├── tsconfig.json │ │ ├── types/ │ │ │ └── static.d.ts │ │ └── web-test-runner.config.js │ ├── app-template-react/ │ │ ├── .npmignore │ │ ├── .prettierrc │ │ ├── README.md │ │ ├── package.json │ │ ├── public/ │ │ │ ├── index.html │ │ │ └── robots.txt │ │ ├── snowpack.config.mjs │ │ ├── src/ │ │ │ ├── App.css │ │ │ ├── App.jsx │ │ │ ├── App.test.jsx │ │ │ ├── index.css │ │ │ └── index.jsx │ │ └── web-test-runner.config.js │ ├── app-template-react-typescript/ │ │ ├── .npmignore │ │ ├── .prettierrc │ │ ├── README.md │ │ ├── package.json │ │ ├── public/ │ │ │ ├── index.html │ │ │ └── robots.txt │ │ ├── snowpack.config.mjs │ │ ├── src/ │ │ │ ├── App.css │ │ │ ├── App.test.tsx │ │ │ ├── App.tsx │ │ │ ├── index.css │ │ │ └── index.tsx │ │ ├── tsconfig.json │ │ ├── types/ │ │ │ └── static.d.ts │ │ └── web-test-runner.config.js │ ├── app-template-svelte/ │ │ ├── .npmignore │ │ ├── README.md │ │ ├── package.json │ │ ├── public/ │ │ │ ├── index.html │ │ │ └── robots.txt │ │ ├── snowpack.config.mjs │ │ ├── src/ │ │ │ ├── App.svelte │ │ │ ├── App.test.js │ │ │ └── index.js │ │ └── web-test-runner.config.js │ ├── app-template-svelte-typescript/ │ │ ├── .npmignore │ │ ├── CHANGELOG.md │ │ ├── README.md │ │ ├── package.json │ │ ├── public/ │ │ │ ├── index.html │ │ │ └── robots.txt │ │ ├── snowpack.config.mjs │ │ ├── src/ │ │ │ ├── App.svelte │ │ │ ├── App.test.ts │ │ │ └── index.ts │ │ ├── svelte.config.js │ │ ├── tsconfig.json │ │ ├── types/ │ │ │ └── static.d.ts │ │ └── web-test-runner.config.js │ ├── app-template-vue/ │ │ ├── .npmignore │ │ ├── README.md │ │ ├── package.json │ │ ├── public/ │ │ │ ├── index.html │ │ │ └── robots.txt │ │ ├── snowpack.config.mjs │ │ └── src/ │ │ ├── App.vue │ │ └── index.js │ ├── app-template-vue-typescript/ │ │ ├── .npmignore │ │ ├── README.md │ │ ├── package.json │ │ ├── public/ │ │ │ ├── index.html │ │ │ └── robots.txt │ │ ├── snowpack.config.mjs │ │ ├── src/ │ │ │ ├── App.vue │ │ │ └── index.js │ │ ├── tsconfig.json │ │ └── types/ │ │ ├── shims-vue.d.ts │ │ └── static.d.ts │ └── cli/ │ ├── README.md │ ├── createSnowpackApp.js │ ├── index.js │ └── package.json ├── docs/ │ ├── README.md │ ├── concepts/ │ │ ├── build-pipeline.md │ │ ├── dev-server.md │ │ ├── hot-module-replacement.md │ │ └── how-snowpack-works.md │ ├── guides/ │ │ ├── babel.md │ │ ├── connecting-tools.md │ │ ├── hmr.md │ │ ├── https-ssl-certificates.md │ │ ├── jest.md │ │ ├── optimize-and-bundle.md │ │ ├── plugins.md │ │ ├── postcss.md │ │ ├── preact.md │ │ ├── react-global-imports.md │ │ ├── react-loadable-components.md │ │ ├── routing.md │ │ ├── sass.md │ │ ├── server-side-render.md │ │ ├── streaming-imports.md │ │ ├── tailwind-css.md │ │ ├── testing.md │ │ ├── upgrade-guide.md │ │ ├── wasm.md │ │ ├── web-test-runner.md │ │ ├── web-worker.md │ │ └── workbox.md │ ├── posts/ │ │ ├── 2020-05-26-snowpack-2-0-release.md │ │ ├── 2020-07-30-snowpack-2-7-release.md │ │ ├── 2020-12-03-snowpack-3-release-candidate.md │ │ └── 2021-01-13-snowpack-3-0.md │ ├── reference/ │ │ ├── cli-command-line-interface.md │ │ ├── common-error-details.md │ │ ├── configuration.md │ │ ├── environment-variables.md │ │ ├── hot-module-replacement.md │ │ ├── javascript-interface.md │ │ ├── plugins.md │ │ └── supported-files.md │ └── tutorials/ │ ├── getting-started.md │ ├── quick-start.md │ ├── react.md │ ├── svelte.md │ └── vue.md ├── esinstall/ │ ├── .gitignore │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── index.esm.mjs │ ├── package.json │ ├── src/ │ │ ├── entrypoints.ts │ │ ├── index.ts │ │ ├── rollup-plugins/ │ │ │ ├── generateProcessPolyfill.ts │ │ │ ├── rollup-plugin-alias.ts │ │ │ ├── rollup-plugin-catch-fetch.ts │ │ │ ├── rollup-plugin-catch-unresolved.ts │ │ │ ├── rollup-plugin-css.ts │ │ │ ├── rollup-plugin-node-process-polyfill.ts │ │ │ ├── rollup-plugin-stats.ts │ │ │ ├── rollup-plugin-strip-source-mapping.ts │ │ │ └── rollup-plugin-wrap-install-targets.ts │ │ ├── stats.ts │ │ ├── types.ts │ │ └── util.ts │ └── tsconfig.json ├── examples/ │ ├── .gitignore │ ├── https-ssl-certificates/ │ │ ├── README.md │ │ ├── index.html │ │ ├── package.json │ │ └── snowpack.config.js │ ├── react-global-imports/ │ │ ├── README.md │ │ ├── babel.config.js │ │ ├── package.json │ │ ├── public/ │ │ │ ├── index.html │ │ │ └── robots.txt │ │ ├── snowpack.config.js │ │ └── src/ │ │ ├── App.css │ │ ├── App.jsx │ │ ├── index.css │ │ └── index.jsx │ ├── react-loadable-components/ │ │ ├── README.md │ │ ├── package.json │ │ ├── public/ │ │ │ ├── index.html │ │ │ └── robots.txt │ │ ├── snowpack.config.js │ │ └── src/ │ │ ├── App.css │ │ ├── App.jsx │ │ ├── Async.jsx │ │ ├── index.css │ │ └── index.jsx │ └── tailwind/ │ ├── README.md │ ├── package.json │ ├── postcss.config.js │ ├── public/ │ │ ├── global.css │ │ └── index.html │ ├── snowpack.config.mjs │ └── tailwind.config.js ├── jest.config.js ├── jest.setup.js ├── lerna.json ├── package.json ├── plugins/ │ ├── plugin-babel/ │ │ ├── CHANGELOG.md │ │ ├── README.md │ │ ├── package.json │ │ ├── plugin.js │ │ ├── test/ │ │ │ └── plugin-babel.test.js │ │ └── worker.js │ ├── plugin-build-script/ │ │ ├── README.md │ │ ├── package.json │ │ ├── plugin.js │ │ └── test/ │ │ └── plugin.test.js │ ├── plugin-dotenv/ │ │ ├── CHANGELOG.md │ │ ├── README.md │ │ ├── package.json │ │ ├── plugin.js │ │ └── test/ │ │ ├── __snapshots__/ │ │ │ └── plugin.test.js.snap │ │ ├── execPlugin.js │ │ └── plugin.test.js │ ├── plugin-optimize/ │ │ ├── CHANGELOG.md │ │ ├── README.md │ │ ├── lib/ │ │ │ ├── css.js │ │ │ ├── html.js │ │ │ └── js.js │ │ ├── package.json │ │ ├── plugin.js │ │ ├── test/ │ │ │ ├── plugin.test.js │ │ │ ├── serializer.js │ │ │ └── stubs/ │ │ │ └── minimal/ │ │ │ ├── do-not-preload-1.js │ │ │ ├── do-not-preload-2.js │ │ │ ├── do-not-preload-3.js │ │ │ ├── esm_example.js │ │ │ ├── index.html │ │ │ ├── index.js │ │ │ ├── package.json │ │ │ ├── style.css │ │ │ └── target-es2018.js │ │ └── util.js │ ├── plugin-postcss/ │ │ ├── CHANGELOG.md │ │ ├── README.md │ │ ├── package.json │ │ ├── plugin.js │ │ ├── test/ │ │ │ ├── fixtures/ │ │ │ │ ├── from/ │ │ │ │ │ ├── from.css │ │ │ │ │ ├── postcss.config.js │ │ │ │ │ └── style.css │ │ │ │ ├── postcss.config.js │ │ │ │ └── style.css │ │ │ └── plugin.test.js │ │ └── worker.js │ ├── plugin-react-refresh/ │ │ ├── CHANGELOG.md │ │ ├── README.md │ │ ├── package.json │ │ ├── plugin.js │ │ └── test/ │ │ ├── __snapshots__/ │ │ │ └── plugin.test.js.snap │ │ ├── plugin.test.js │ │ └── stubs/ │ │ ├── stub.html │ │ └── stub.js │ ├── plugin-run-script/ │ │ ├── README.md │ │ ├── package.json │ │ ├── plugin.js │ │ └── test/ │ │ └── plugin.test.js │ ├── plugin-sass/ │ │ ├── CHANGELOG.md │ │ ├── README.md │ │ ├── package.json │ │ ├── plugin.js │ │ └── test/ │ │ ├── __snapshots__/ │ │ │ └── plugin.test.js.snap │ │ ├── fixtures/ │ │ │ ├── bad/ │ │ │ │ └── bad.scss │ │ │ ├── sass/ │ │ │ │ ├── App.sass │ │ │ │ ├── _base.sass │ │ │ │ └── folder/ │ │ │ │ ├── _child-partial.sass │ │ │ │ └── _index.sass │ │ │ └── scss/ │ │ │ ├── App.scss │ │ │ ├── _base.scss │ │ │ └── folder/ │ │ │ └── _index.scss │ │ ├── plugin-mocked.test.js │ │ └── plugin.test.js │ ├── plugin-svelte/ │ │ ├── CHANGELOG.md │ │ ├── README.md │ │ ├── package.json │ │ ├── plugin.js │ │ └── test/ │ │ ├── Button.svelte │ │ ├── custom-config.js │ │ ├── plugin.test.js │ │ └── svelte.config.js │ ├── plugin-typescript/ │ │ ├── CHANGELOG.md │ │ ├── README.md │ │ ├── package.json │ │ ├── plugin.js │ │ └── test/ │ │ └── plugin.test.js │ ├── plugin-vue/ │ │ ├── CHANGELOG.md │ │ ├── README.md │ │ ├── package.json │ │ ├── plugin-tsx-jsx.js │ │ ├── plugin.js │ │ ├── src/ │ │ │ └── script-compilers.js │ │ └── test/ │ │ ├── __snapshots__/ │ │ │ ├── plugin-tsx-jsx.test.js.snap │ │ │ ├── plugin-vue-ts-tsx-jsx.test.js.snap │ │ │ ├── plugin.test.js.snap │ │ │ └── script-compilers.test.js.snap │ │ ├── plugin-tsx-jsx.test.js │ │ ├── plugin-vue-ts-tsx-jsx.test.js │ │ ├── plugin.test.js │ │ ├── script-compilers.test.js │ │ └── stubs/ │ │ ├── JsxContent.jsx │ │ ├── TsContent.ts │ │ ├── TsxContent.tsx │ │ ├── VueContent.vue │ │ ├── VueContentJsx.vue │ │ ├── VueContentOnlyTpl.vue │ │ ├── VueContentStyleScoped.vue │ │ ├── VueContentTs.vue │ │ ├── VueContentTsx.vue │ │ └── tsconfig.json │ ├── plugin-webpack/ │ │ ├── .gitignore │ │ ├── CHANGELOG.md │ │ ├── README.md │ │ ├── package.json │ │ ├── plugin.js │ │ ├── plugins/ │ │ │ ├── import-meta-fix.js │ │ │ └── proxy-import-resolve.js │ │ └── test/ │ │ ├── __snapshots__/ │ │ │ └── plugin.test.js.snap │ │ ├── plugin.test.js │ │ ├── readFilesSync.js │ │ └── stubs/ │ │ ├── minimal/ │ │ │ ├── index.html │ │ │ ├── index.js │ │ │ ├── package.json │ │ │ └── styles.css │ │ └── multiple-entrypoints/ │ │ ├── admin/ │ │ │ ├── index.html │ │ │ └── index.js │ │ ├── index.html │ │ ├── index.js │ │ └── package.json │ └── web-test-runner-plugin/ │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ └── plugin.js ├── scripts/ │ ├── release-all.js │ └── release.cjs ├── skypack/ │ ├── .gitignore │ ├── .prettierrc │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── index.esm.mjs │ ├── package.json │ ├── src/ │ │ ├── index.ts │ │ ├── rollup-plugin-remote-cdn.ts │ │ └── util.ts │ └── tsconfig.json ├── snowpack/ │ ├── .gitignore │ ├── .npmignore │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── assets/ │ │ ├── hmr-client.js │ │ ├── hmr-error-overlay.js │ │ ├── openChrome.appleScript │ │ ├── require-or-import.js │ │ └── snowpack-init-file.js │ ├── index.bin.js │ ├── package.json │ ├── src/ │ │ ├── build/ │ │ │ ├── build-import-proxy.ts │ │ │ ├── build-pipeline.ts │ │ │ ├── file-builder.ts │ │ │ ├── file-urls.ts │ │ │ ├── import-css.ts │ │ │ ├── import-resolver.ts │ │ │ ├── import-sri.ts │ │ │ ├── optimize.ts │ │ │ └── process.ts │ │ ├── commands/ │ │ │ ├── add-rm.ts │ │ │ ├── build.ts │ │ │ ├── dev.ts │ │ │ ├── init.ts │ │ │ ├── paint.ts │ │ │ └── prepare.ts │ │ ├── config.ts │ │ ├── dev/ │ │ │ └── hmr.ts │ │ ├── hmr-server-engine.ts │ │ ├── index.ts │ │ ├── lexer-util.ts │ │ ├── logger.ts │ │ ├── plugins/ │ │ │ └── plugin-esbuild.ts │ │ ├── rewrite-imports.ts │ │ ├── scan-import-glob.ts │ │ ├── scan-imports.ts │ │ ├── sources/ │ │ │ ├── local-install.ts │ │ │ ├── local.ts │ │ │ ├── remote.ts │ │ │ └── util.ts │ │ ├── ssr-loader/ │ │ │ ├── index.ts │ │ │ ├── sourcemaps.ts │ │ │ └── transform.ts │ │ ├── types.ts │ │ └── util.ts │ ├── tsconfig.cjs.json │ └── tsconfig.json ├── test/ │ ├── build/ │ │ ├── config-loading-esm-package/ │ │ │ ├── config-loading-esm-package.test.js │ │ │ ├── package.json │ │ │ ├── snowpack.config.js │ │ │ └── src/ │ │ │ └── index.js │ │ ├── config-loading-mjs/ │ │ │ ├── config-loading-mjs.test.js │ │ │ ├── package.json │ │ │ ├── snowpack.config.mjs │ │ │ └── src/ │ │ │ └── index.js │ │ ├── config-path/ │ │ │ ├── config-path.test.js │ │ │ ├── index.js │ │ │ ├── my-config-file.js │ │ │ └── package.json │ │ ├── entrypoint-ids/ │ │ │ ├── entrypoint-ids.test.js │ │ │ ├── package.json │ │ │ └── src/ │ │ │ └── index.js │ │ ├── import-assets/ │ │ │ ├── import-assets.test.js │ │ │ ├── package.json │ │ │ ├── snowpack.config.json │ │ │ └── src/ │ │ │ ├── index.js │ │ │ └── styles.css │ │ ├── package-bootstrap/ │ │ │ ├── package-bootstrap.test.js │ │ │ ├── package.json │ │ │ └── src/ │ │ │ ├── index.js │ │ │ └── styles.css │ │ ├── package-workspace/ │ │ │ ├── package-workspace.test.js │ │ │ ├── package.json │ │ │ ├── snowpack.config.js │ │ │ └── src/ │ │ │ ├── index.html │ │ │ └── index.svelte │ │ ├── plugin-build-script/ │ │ │ ├── package.json │ │ │ ├── plugin-build-script.test.js │ │ │ ├── snowpack.config.js │ │ │ └── src/ │ │ │ └── index.ts │ │ ├── plugin-hook-optimize/ │ │ │ ├── custom-optimize-plugin.js │ │ │ ├── package.json │ │ │ ├── plugin-hook-optimize.test.js │ │ │ ├── snowpack.config.json │ │ │ └── src/ │ │ │ ├── index.js │ │ │ └── styles.css │ │ ├── plugin-run-script/ │ │ │ ├── package.json │ │ │ ├── plugin-run-script.test.js │ │ │ ├── public/ │ │ │ │ └── css/ │ │ │ │ └── index.css │ │ │ ├── snowpack.config.js │ │ │ └── src/ │ │ │ └── css/ │ │ │ └── index.scss │ │ ├── prepare-external-package/ │ │ │ ├── package.json │ │ │ ├── prepare-external-package.test.js │ │ │ └── src/ │ │ │ └── index.js │ │ ├── react-lazy-bundle/ │ │ │ ├── package.json │ │ │ ├── public/ │ │ │ │ └── index.html │ │ │ ├── react-lazy.test.js │ │ │ ├── snowpack.config.js │ │ │ └── src/ │ │ │ ├── components/ │ │ │ │ ├── App.jsx │ │ │ │ └── Articles.jsx │ │ │ └── index.jsx │ │ └── test-workspace-component/ │ │ ├── Layout.ts │ │ ├── README.md │ │ ├── SvelteComponent.svelte │ │ ├── index.mjs │ │ ├── package.json │ │ └── works-without-extension.ts │ ├── create-snowpack-app/ │ │ ├── __snapshots__/ │ │ │ └── create-snowpack-app.test.js.snap │ │ └── create-snowpack-app.test.js │ ├── esinstall/ │ │ ├── alias/ │ │ │ ├── alias.test.js │ │ │ └── package.json │ │ ├── cjs-autodetect-exports/ │ │ │ ├── .gitignore │ │ │ ├── cjs-autodetect-exports.test.js │ │ │ ├── package.json │ │ │ └── packages/ │ │ │ ├── cjs-invalid-exports/ │ │ │ │ ├── entrypoint.js │ │ │ │ └── package.json │ │ │ └── cjs-valid-exports/ │ │ │ ├── entrypoint.js │ │ │ └── package.json │ │ ├── config-package-svelte/ │ │ │ ├── config-package-svelte.test.js │ │ │ ├── package.json │ │ │ └── src/ │ │ │ └── index.js │ │ ├── dep-list-simple/ │ │ │ ├── __snapshots__ │ │ │ ├── dep-list-simple.test.js │ │ │ └── package.json │ │ ├── error-missing-dep/ │ │ │ ├── __snapshots__ │ │ │ ├── error-missing-dep.test.js │ │ │ └── package.json │ │ ├── esinstall-test-utils.js │ │ ├── exclude-external-packages/ │ │ │ ├── __snapshots__ │ │ │ ├── exclude-external-packages.test.js │ │ │ └── package.json │ │ ├── exports-only/ │ │ │ ├── .gitignore │ │ │ ├── exports-only.test.js │ │ │ ├── mod.js │ │ │ ├── package.json │ │ │ ├── pkg/ │ │ │ │ ├── mod.mjs │ │ │ │ └── package.json │ │ │ └── snowpack.config.js │ │ ├── exports-only-no-main/ │ │ │ ├── .gitignore │ │ │ ├── exports-only-no-main.test.js │ │ │ ├── mod.js │ │ │ ├── package.json │ │ │ ├── pkg/ │ │ │ │ ├── mod.mjs │ │ │ │ └── package.json │ │ │ └── snowpack.config.js │ │ ├── import-assets/ │ │ │ ├── .gitignore │ │ │ ├── import-assets.test.js │ │ │ ├── package.json │ │ │ └── packages/ │ │ │ └── mock-pkg-install-assets/ │ │ │ ├── css.css │ │ │ ├── json.json │ │ │ └── package.json │ │ ├── import-astro/ │ │ │ ├── .gitignore │ │ │ ├── import-astro.test.js │ │ │ ├── package.json │ │ │ └── packages/ │ │ │ └── components/ │ │ │ ├── Wow.astro │ │ │ └── package.json │ │ ├── import-missing/ │ │ │ ├── import-missing.test.js │ │ │ ├── package.json │ │ │ ├── packages/ │ │ │ │ ├── @material/ │ │ │ │ │ └── animation/ │ │ │ │ │ ├── LICENSE │ │ │ │ │ ├── README.md │ │ │ │ │ ├── _functions.import.scss │ │ │ │ │ ├── _functions.scss │ │ │ │ │ ├── _index.scss │ │ │ │ │ ├── _variables.import.scss │ │ │ │ │ ├── _variables.scss │ │ │ │ │ ├── dist/ │ │ │ │ │ │ ├── mdc.animation.d.ts │ │ │ │ │ │ └── mdc.animation.js │ │ │ │ │ ├── index.d.ts │ │ │ │ │ ├── index.js │ │ │ │ │ ├── package.json │ │ │ │ │ ├── types.d.ts │ │ │ │ │ ├── types.js │ │ │ │ │ ├── util.d.ts │ │ │ │ │ └── util.js │ │ │ │ └── tslib/ │ │ │ │ ├── CopyrightNotice.txt │ │ │ │ ├── LICENSE.txt │ │ │ │ ├── package.json │ │ │ │ ├── tslib.d.ts │ │ │ │ ├── tslib.es6.html │ │ │ │ ├── tslib.es6.js │ │ │ │ ├── tslib.html │ │ │ │ └── tslib.js │ │ │ └── src/ │ │ │ └── index.js │ │ ├── import-named-from-default/ │ │ │ ├── import-named-from-default.test.js │ │ │ ├── package/ │ │ │ │ └── default-only-esm/ │ │ │ │ ├── index.js │ │ │ │ └── package.json │ │ │ └── package.json │ │ ├── import-node-builtin/ │ │ │ ├── .gitignore │ │ │ ├── error-node-builtin-unresolved.test.js │ │ │ ├── package.json │ │ │ └── packages/ │ │ │ └── bad-node-builtin-pkg/ │ │ │ ├── entrypoint.js │ │ │ └── package.json │ │ ├── import-nothing/ │ │ │ ├── import-nothing.test.js │ │ │ └── package.json │ │ ├── import-types/ │ │ │ ├── .gitignore │ │ │ ├── import-types.test.js │ │ │ ├── package.json │ │ │ └── packages/ │ │ │ └── type-only-pkg/ │ │ │ ├── index.d.ts │ │ │ └── package.json │ │ ├── named-exports/ │ │ │ ├── .gitignore │ │ │ ├── named-exports.test.js │ │ │ ├── package.json │ │ │ └── packages/ │ │ │ ├── cjs-named-exports-obj/ │ │ │ │ ├── entrypoint.js │ │ │ │ └── package.json │ │ │ ├── cjs-named-exports-reexported/ │ │ │ │ ├── entrypoint.js │ │ │ │ ├── package.json │ │ │ │ └── reexported.js │ │ │ ├── cjs-named-exports-simple/ │ │ │ │ ├── entrypoint.js │ │ │ │ └── package.json │ │ │ └── umd-named-exports/ │ │ │ ├── autolayout.js │ │ │ └── package.json │ │ ├── node-env/ │ │ │ ├── .gitignore │ │ │ ├── node-env.test.js │ │ │ ├── package.json │ │ │ └── packages/ │ │ │ └── node-env-mock-pkg/ │ │ │ ├── entrypoint.js │ │ │ └── package.json │ │ ├── package-entrypoints/ │ │ │ ├── .gitignore │ │ │ ├── browser-dot/ │ │ │ │ ├── entrypoint.js │ │ │ │ └── package.json │ │ │ ├── browser-dot-slash/ │ │ │ │ ├── entrypoint.js │ │ │ │ └── package.json │ │ │ ├── browser-dot-slash-index/ │ │ │ │ ├── entrypoint.js │ │ │ │ └── package.json │ │ │ ├── browser-dot-slash-index-js/ │ │ │ │ ├── entrypoint.js │ │ │ │ └── package.json │ │ │ ├── browser-index/ │ │ │ │ ├── entrypoint.js │ │ │ │ └── package.json │ │ │ ├── browser-index-js/ │ │ │ │ ├── entrypoint.js │ │ │ │ └── package.json │ │ │ ├── browser-no-valid/ │ │ │ │ ├── entrypoint.js │ │ │ │ └── package.json │ │ │ ├── browser-path/ │ │ │ │ ├── entrypoint.js │ │ │ │ └── package.json │ │ │ ├── export-map-dot-no-slash/ │ │ │ │ ├── entrypoint.js │ │ │ │ └── package.json │ │ │ ├── export-map-dot-slash/ │ │ │ │ ├── entrypoint.js │ │ │ │ └── package.json │ │ │ ├── export-map-internal-imports/ │ │ │ │ ├── entrypoint.js │ │ │ │ ├── imported-by-entrypoint.js │ │ │ │ ├── imports-entrypoint.js │ │ │ │ └── package.json │ │ │ ├── export-map-object-browser/ │ │ │ │ ├── entrypoint.js │ │ │ │ └── package.json │ │ │ ├── export-map-object-browser-object/ │ │ │ │ ├── entrypoint.js │ │ │ │ └── package.json │ │ │ ├── export-map-object-default/ │ │ │ │ ├── entrypoint.js │ │ │ │ └── package.json │ │ │ ├── export-map-object-import/ │ │ │ │ ├── entrypoint.js │ │ │ │ └── package.json │ │ │ ├── export-map-object-no-key/ │ │ │ │ ├── entrypoint.js │ │ │ │ └── package.json │ │ │ ├── export-map-object-require/ │ │ │ │ ├── entrypoint.js │ │ │ │ └── package.json │ │ │ ├── export-map-star/ │ │ │ │ ├── entrypoint.js │ │ │ │ ├── package.json │ │ │ │ └── src/ │ │ │ │ └── extras/ │ │ │ │ ├── one.js │ │ │ │ ├── other.css │ │ │ │ ├── three.js │ │ │ │ └── two.js │ │ │ ├── export-map-trailing-slash/ │ │ │ │ ├── dist/ │ │ │ │ │ ├── esm/ │ │ │ │ │ │ └── helpers.js │ │ │ │ │ └── index.js │ │ │ │ ├── entrypoint.js │ │ │ │ ├── package.json │ │ │ │ └── src/ │ │ │ │ ├── extras/ │ │ │ │ │ ├── one.js │ │ │ │ │ ├── other.css │ │ │ │ │ ├── three.js │ │ │ │ │ └── two.js │ │ │ │ └── more/ │ │ │ │ └── one.js │ │ │ ├── implicit-main/ │ │ │ │ ├── index.d.ts │ │ │ │ ├── index.js │ │ │ │ └── package.json │ │ │ ├── jsnext-main/ │ │ │ │ ├── entrypoint.js │ │ │ │ └── package.json │ │ │ ├── main-folder/ │ │ │ │ ├── entrypoint/ │ │ │ │ │ └── index.js │ │ │ │ └── package.json │ │ │ ├── module/ │ │ │ │ ├── entrypoint.js │ │ │ │ └── package.json │ │ │ ├── package-entrypoints-browser.test.js │ │ │ ├── package-entrypoints-export-map.test.js │ │ │ ├── package-entrypoints-general.test.js │ │ │ ├── package.json │ │ │ └── pkg-with-dot.in-the-name/ │ │ │ ├── entrypoint.js │ │ │ └── package.json │ │ ├── package-node-fetch/ │ │ │ ├── .gitignore │ │ │ ├── package-node-fetch.test.js │ │ │ ├── package.json │ │ │ └── packages/ │ │ │ └── dep-node-fetch-mock-pkg/ │ │ │ ├── entrypoint.js │ │ │ └── package.json │ │ ├── package-react/ │ │ │ ├── __snapshots__ │ │ │ ├── package-react.test.js │ │ │ ├── package.json │ │ │ └── src/ │ │ │ └── index.js │ │ ├── polyfill-node/ │ │ │ ├── .gitignore │ │ │ ├── package.json │ │ │ ├── packages/ │ │ │ │ └── node-builtin-pkg/ │ │ │ │ ├── entrypoint.js │ │ │ │ └── package.json │ │ │ └── polyfill-node.test.js │ │ ├── rollup/ │ │ │ ├── .gitignore │ │ │ ├── package.json │ │ │ ├── rollup.test.js │ │ │ └── src/ │ │ │ └── index.js │ │ ├── source-map-strip/ │ │ │ ├── __snapshots__ │ │ │ ├── package.json │ │ │ └── source-map-strip.test.js │ │ ├── sub-package-json/ │ │ │ ├── .gitignore │ │ │ ├── package.json │ │ │ └── sub-package-json.test.js │ │ └── tree-shake-expression/ │ │ ├── .gitignore │ │ ├── inner-module/ │ │ │ ├── main.js │ │ │ └── package.json │ │ ├── package.json │ │ └── tree-shake-expression.test.js │ ├── esinstall.api.test.js │ ├── fixture-utils.js │ ├── rollup-plugins.test.js │ ├── snowpack/ │ │ ├── __template__/ │ │ │ └── index.test.js │ │ ├── cdnUrls/ │ │ │ └── index.test.js │ │ ├── config/ │ │ │ ├── alias/ │ │ │ │ ├── __snapshots__/ │ │ │ │ │ └── index.test.js.snap │ │ │ │ └── index.test.js │ │ │ ├── buildOptions.baseUrl/ │ │ │ │ └── index.test.js │ │ │ ├── buildOptions.jsxInject/ │ │ │ │ └── index.test.js │ │ │ ├── buildOptions.metaUrlPath/ │ │ │ │ └── index.test.js │ │ │ ├── buildOptions.out/ │ │ │ │ └── index.test.js │ │ │ ├── env/ │ │ │ │ └── index.test.js │ │ │ ├── invalid/ │ │ │ │ └── index.test.js │ │ │ ├── mode/ │ │ │ │ └── index.test.js │ │ │ ├── mount/ │ │ │ │ └── index.test.js │ │ │ ├── optimize/ │ │ │ │ └── index.test.js │ │ │ ├── packageOptions.external/ │ │ │ │ └── index.test.js │ │ │ ├── packageOptions.packageLookupFields/ │ │ │ │ └── index.test.js │ │ │ ├── packageOptions.source/ │ │ │ │ └── index.test.js │ │ │ ├── plugins/ │ │ │ │ ├── extends/ │ │ │ │ │ └── index.test.js │ │ │ │ └── instantiatedObject/ │ │ │ │ ├── __snapshots__/ │ │ │ │ │ └── index.test.js.snap │ │ │ │ └── index.test.js │ │ │ └── withExtension/ │ │ │ └── index.test.js │ │ ├── cssModules/ │ │ │ └── index.test.js │ │ ├── import/ │ │ │ ├── css.ts/ │ │ │ │ └── index.test.js │ │ │ ├── dotFolder/ │ │ │ │ └── index.test.js │ │ │ ├── glob/ │ │ │ │ └── index.test.js │ │ │ ├── json/ │ │ │ │ ├── __snapshots__/ │ │ │ │ │ └── index.test.js.snap │ │ │ │ └── index.test.js │ │ │ └── ts/ │ │ │ └── index.test.js │ │ ├── import-sri.test.ts │ │ ├── moduleResolution/ │ │ │ └── index.test.js │ │ ├── namedImport/ │ │ │ ├── __snapshots__/ │ │ │ │ └── index.test.js.snap │ │ │ └── index.test.js │ │ ├── package/ │ │ │ ├── @nivo/ │ │ │ │ └── index.test.js │ │ │ ├── bootstrap/ │ │ │ │ └── index.test.js │ │ │ ├── tippy/ │ │ │ │ └── index.test.js │ │ │ └── workspace/ │ │ │ └── index.test.js │ │ ├── plugin/ │ │ │ ├── babel/ │ │ │ │ └── index.test.js │ │ │ ├── buildScript/ │ │ │ │ └── index.test.js │ │ │ ├── custom/ │ │ │ │ └── index.test.js │ │ │ ├── customTransform/ │ │ │ │ └── index.test.js │ │ │ ├── runScript/ │ │ │ │ └── index.test.js │ │ │ ├── sass/ │ │ │ │ └── index.test.js │ │ │ ├── svelte/ │ │ │ │ └── index.test.js │ │ │ └── vue/ │ │ │ └── index.test.js │ │ ├── runtime/ │ │ │ └── runtime.test.js │ │ ├── utf8/ │ │ │ └── index.test.js │ │ └── util.test.ts │ └── test-utils.js ├── test-dev/ │ ├── README.md │ ├── __snapshots__/ │ │ └── dev.test.ts.snap │ ├── dev.test.ts │ ├── smoke/ │ │ ├── package.json │ │ ├── public/ │ │ │ ├── about.tmpl │ │ │ ├── index.css │ │ │ └── index.html │ │ ├── snowpack.config.js │ │ └── src/ │ │ └── index.js │ ├── smoke-secure-1/ │ │ ├── package.json │ │ ├── public/ │ │ │ ├── about.tmpl │ │ │ ├── index.css │ │ │ └── index.html │ │ ├── snowpack.config.js │ │ ├── snowpack.crt │ │ ├── snowpack.key │ │ └── src/ │ │ └── index.js │ └── smoke-secure-2/ │ ├── package.json │ ├── public/ │ │ ├── about.tmpl │ │ ├── index.css │ │ └── index.html │ ├── snowpack.config.js │ ├── src/ │ │ └── index.js │ └── tls/ │ ├── certificate.pem │ └── key.pem └── www/ ├── .gitignore ├── LICENSE ├── README.md ├── astro.config.mjs ├── package.json ├── public/ │ ├── favicon/ │ │ └── site.webmanifest │ ├── img/ │ │ ├── snowpackskypack.webm │ │ └── streaming-imports-demo.webm │ ├── js/ │ │ └── index.js │ ├── robots.txt │ └── styles/ │ ├── _animations.scss │ ├── _card-grid.scss │ ├── _github-markdown.scss │ ├── _globals.scss │ ├── _prism.scss │ ├── _typography.scss │ ├── _utils.scss │ ├── _var.scss │ └── global.scss ├── scripts/ │ └── copy.js └── src/ ├── components/ │ ├── Banner.astro │ ├── BaseHead.astro │ ├── BaseLayout.astro │ ├── Button.astro │ ├── Card.css │ ├── Card.jsx │ ├── CompanyLogo.jsx │ ├── Hero.astro │ ├── MainLayout.astro │ ├── Menu.astro │ ├── Nav.astro │ ├── NewsAssets.svelte │ ├── NewsTitle.vue │ ├── PluginSearchPage.jsx │ ├── PluginSearchPage.module.css │ ├── PokemonLookup.astro │ ├── Subnav.astro │ ├── docsearch.js │ └── index.ts ├── data/ │ ├── news.json │ └── users.json ├── layouts/ │ ├── content-with-cover.astro │ ├── content.astro │ └── post.astro └── pages/ ├── 404.astro ├── guides.astro ├── index.astro ├── news.astro └── plugins.astro ================================================ FILE CONTENTS ================================================ ================================================ FILE: .changeset/README.md ================================================ # Changesets Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works with multi-package repos, or single-package repos to help you version and publish your code. You can find the full documentation for it [in our repository](https://github.com/changesets/changesets) We have a quick list of common questions to get you started engaging with this project in [our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md) ================================================ FILE: .changeset/config.json ================================================ { "$schema": "https://unpkg.com/@changesets/config@1.6.0/schema.json", "changelog": "@changesets/cli/changelog", "commit": false, "linked": [], "access": "public", "baseBranch": "main", "ignore": ["@snowpack/test-*"] } ================================================ FILE: .editorconfig ================================================ root = true [*] charset = utf-8 end_of_line = lf indent_style = space indent_size = 2 insert_final_newline = true ================================================ FILE: .github/CODEOWNERS ================================================ * @snowpackjs/maintainers docs/* @snowpackjs/docs ================================================ FILE: .github/ISSUE_TEMPLATE/---bug-report.yml ================================================ name: "\U0001F41B Bug Report" description: Report an issue or possible bug title: "\U0001F41B BUG:" labels: [] assignees: [] body: - type: markdown attributes: value: | Thank you for taking the time to file a bug report! Please fill out this form as completely as possible. - type: checkboxes attributes: label: Quick checklist options: - label: I am using the **latest version of Snowpack** and all plugins. - type: input attributes: label: What package manager are you using? placeholder: npm, yarn, pnpm validations: required: true - type: input attributes: label: What operating system are you using? placeholder: macOS, Windows, Linux validations: required: true - type: textarea attributes: label: Describe the bug description: A clear and concise description of what the bug is. validations: required: true - type: textarea attributes: label: Steps to reproduce description: Describe the bug in steps that we can reproduce ourselves. value: | 1. `npx create-snowpack-app` using template 2. ... 3. ... 4. ... 5. Error! Describe what went wrong (and what was expected instead)... validations: required: true - type: input attributes: label: Link to minimal reproducible example (optional) description: 'Issues with easy reproductions are more likely to get fixed, faster.' placeholder: 'https://github.com/username/repo' ================================================ FILE: .github/ISSUE_TEMPLATE/---feature-request.yml ================================================ name: "\U0001F4A1 Feature Request" description: 'Submit an RFC or suggest an idea for this project' title: "\U0001F4A1 RFC: " labels: ['feature'] assignees: [] body: - type: markdown attributes: value: Thanks for taking the time to suggest a new feature! Please fill out this form as completely as possible. - type: textarea attributes: label: Motivation description: | A quick, clear and concise description of what the problem is. **Please include links to relevant issues, Discord convos, and anything else.** placeholder: I want to be able to... validations: required: true - type: textarea attributes: label: Proposed solution description: Your take on one (or more) possible solution(s) to problem. value: | ### Possible solutions ### Alternatives considered ### Risks, downsides, and/or tradeoffs validations: required: true - type: textarea attributes: label: Detailed design description: | 🛑 **Just looking for feedback on an idea? Leave this section blank.** Otherwise, explain the exact steps required to implement this change. Include specific details that would help someone implement this feature. - type: textarea attributes: label: Open questions description: | Are there any open questions remaining? List them here. - type: checkboxes attributes: label: Help make it happen! description: 'Tip: RFCs with contributing authors are much more likely to get done!' options: - label: I am willing to submit a PR to implement this change. - label: I am willing to submit a PR to implement this change, but would need some guidance. - label: I am not willing to submit a PR to implement this change. validations: required: true ================================================ FILE: .github/ISSUE_TEMPLATE/config.yml ================================================ blank_issues_enabled: false contact_links: - name: 💬 Everything else about: Ask questions and get help from our community! url: https://github.com/withastro/snowpack/discussions ================================================ FILE: .github/ISSUE_TEMPLATE.md ================================================ **Original Discussion:** **/cc** ================================================ FILE: .github/PULL_REQUEST_TEMPLATE.md ================================================ ## Changes ## Testing ## Docs ================================================ FILE: .github/workflows/format.yml ================================================ name: 'Format Code' on: push: branches: - main env: node_version: 14 jobs: format: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 with: ref: ${{ github.head_ref }} - uses: actions/setup-node@v2 with: node-version: ${{ env.node_version }} - name: yarn install run: yarn --frozen-lockfile env: CI: true - name: yarn format run: yarn format - name: Commit changes uses: stefanzweifel/git-auto-commit-action@v4 with: commit_message: '[ci] yarn format' branch: ${{ github.head_ref }} push_options: --force ================================================ FILE: .github/workflows/lint.yml ================================================ name: Lint Code on: push: branches: - main pull_request: env: node_version: 14 jobs: lint: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: Use Node.js ${{ env.node_version }} uses: actions/setup-node@v2 with: node-version: ${{ env.node_version }} - name: lint run: | yarn --ignore-engines --frozen-lockfile yarn build yarn lint ================================================ FILE: .github/workflows/release.yml ================================================ # Inspired by https://bret.io/projects/package-automation/ name: Create Release on: workflow_dispatch: inputs: tag: description: 'what kind of release? (ex: "next", "latest")' required: true packagePath: description: 'path to package? (ex: "snowpack", "plugins/plugin-babel")' required: true env: node_version: 14 jobs: version_and_release: runs-on: ubuntu-latest steps: # SETUP: - uses: actions/checkout@v2 with: fetch-depth: 0 - name: Use Node.js ${{ env.node_version }} uses: actions/setup-node@v2 with: node-version: ${{ env.node_version }} # (REQUIRED) setting a registry enables the NODE_AUTH_TOKEN env variable where we can set an npm token. registry-url: 'https://registry.npmjs.org' - run: git config --global user.email "github-ci@snowpack.dev" - run: git config --global user.name " ${{ github.actor }}" - run: yarn --frozen-lockfile # PUBLISH: - run: node -e "require('./scripts/release.cjs')('${{ github.event.inputs.packagePath }}', '${{ github.event.inputs.tag }}')" env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} ================================================ FILE: .github/workflows/test.yml ================================================ name: Test Code on: push: branches: - main pull_request: jobs: test: runs-on: ${{ matrix.os }} strategy: matrix: os: [ubuntu-latest, windows-latest] node-version: [12.x, 14.x, 16.x] steps: - uses: actions/checkout@v2 - name: Use Node.js ${{ matrix.node-version }} uses: actions/setup-node@v2 with: node-version: ${{ matrix.node-version }} - name: build run: | yarn --ignore-engines --frozen-lockfile yarn build env: CI: true - name: test run: yarn test env: CI: true ================================================ FILE: .gitignore ================================================ .DS_Store .build .idea .snowpack .vercel lerna-debug.log node_modules package-lock.json create-snowpack-app/*/build test/__temp__ test/build/**/build test/build/**/TEST_BUILD_OUT test/build/**/web_modules test/create-snowpack-app/test-install test/esinstall/**/web_modules yarn-error.log # Ignore files that are copied from /docs www/_template/**/*.md !www/_template/posts/**/*.md ================================================ FILE: .prettierignore ================================================ vendor dist test/build/**/build create-snowpack-app/**/build packages pkg TEST_BUILD_OUT web_modules test/create-snowpack-app/test-install .snowpack ================================================ FILE: CHANGELOG.md ================================================ # Changelog ## Version 3.x The CHANGELOG.md for the current 3.x version of Snowpack is maintained in the `/snowpack` directory at: https://github.com/withastro/snowpack/blob/main/snowpack/CHANGELOG.md ## Version 2.x The CHANGELOG.md for version 2.x is currently maintained in a discussion so that you can subscribe for updates: **https://github.com/withastro/snowpack/discussions/1183** ================================================ FILE: CODE_OF_CONDUCT.md ================================================ # Contributor Covenant Code of Conduct ## Our Pledge In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. ## Our Standards Examples of behavior that contributes to creating a positive environment include: - Using welcoming and inclusive language - Being respectful of differing viewpoints and experiences - Gracefully accepting constructive criticism - Focusing on what is best for the community - Showing empathy towards other community members Examples of unacceptable behavior by participants include: - The use of sexualized language or imagery and unwelcome sexual attention or advances - Trolling, insulting/derogatory comments, and personal or political attacks - Public or private harassment - Publishing others' private information, such as a physical or electronic address, without explicit permission - Other conduct which could reasonably be considered inappropriate in a professional setting ## Our Responsibilities Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. ## Scope This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at info@pika.dev. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html [homepage]: https://www.contributor-covenant.org For answers to common questions about this code of conduct, see https://www.contributor-covenant.org/faq ================================================ FILE: CONTRIBUTING.md ================================================ # Contributions Welcome! Interested in contributing? We'd love your help! If you run into problems or find something confusing, please share it with us in [this discussion](https://github.com/withastro/snowpack/discussions/958). A great experience for new contributors is very important to us! Please note that all activity on the [`snowpackjs/snowpack` repository](https://github.com/withastro/snowpack) and our [Discord][discord] is moderated and will be strictly enforced under Snowpack's [Contributor Code of Conduct](CODE_OF_CONDUCT.md). Our [issue tracker](https://github.com/withastro/snowpack/issues) is always organized with a selection of high-priority bugs, feature requests, and ["help wanted!"](https://github.com/withastro/snowpack/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22)/["good first issue"](https://github.com/withastro/snowpack/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22) items. For general package troubleshooting and discussions, check out our [Package Community](https://www.pika.dev/npm/snowpack/discuss) discussion board. ## Requirements Snowpack uses [yarn workspaces](https://classic.yarnpkg.com/) to manage multiple packages contained in this repository. To contribute, [make sure that you have Yarn installed on your machine](https://classic.yarnpkg.com/en/docs/install). ## Initial setup ```bash git clone https://github.com/withastro/snowpack.git cd snowpack yarn ``` ## Checking out and building a branch ```bash git checkout some-branch-name yarn # in case dependencies have changed yarn build # re-builds all packages # that's it! Your monorepo is set up and ready to run/test. ``` ## Build after changes Some packages in the repo are written in JavaScript, and require no build step. Others (like Snowpack itself) are written in TypeScript, and require a build step to run. ```bash # Option 1: A one-time build step yarn build # Option 2: Start a persistent TypeScript watcher, recompiling on every change # Recommended for active development, when many changes are required yarn build:watch ``` ## Updating and adding packages Since this is a monorepo with several packages, if you want to update/add packages in the subrepos like `create-snowpack-app/app-template-11ty` you'll want to run the commands in the target subdirectory like ```bash cd create-snowpack-app/app-template-vue yarn add vue@latest ``` ## Tests We recommend running all tests before you submit a PR. Tests will not work unless you have run a build `yarn build`. ### Running tests From the repository's root folder, run ```bash yarn build yarn test yarn test:dev # sometimes flaky on windows, see #1171 ``` ### Snapshot tests _Update March 2021: we’re working on improving this and would love your help converting old test directories to smaller TypeScript tests/fixtures!_ All new tests should live in either `test/snowpack`, `test/esinstall`, or the `test` directory of one of our plugins (ex: `plugins/plugin-dotenv/test/plugin.test.js`). `test/build` is considered legacy and is in the process of getting converted into `test/snowpack`. The way our snapshot tests work is they test Snowpack by building a project. Because Snowpack's build method uses the dev server internally, this is a good way to test the build behavior of both dev & build together. You'll almost always have a "failed" snapshot test when you make a contribution because your new change will make the final build different. You'll want to take a new snapshot. To do this run: ```bash yarn test -u ``` You'll notice this changes the snapshot file. Commit this change and submit it along with your PR. ### Filtering tests Jest supports basic test filtering to only run a subset of our test suite. The [`--testNamePattern`](https://jestjs.io/docs/en/cli#--testnamepatternregex) flag also works as well. ```bash yarn test treeshake ``` ## Run local snowpack in another project You can run your local snowpack by path ```bash yarn build cd path/to/some-other-project /path/to/snowpack-repository/snowpack/index.bin.js dev --verbose --reload ``` Or by linking the global `snowpack` library to your local clone ```bash cd snowpack npm link cd path/to/some-other-project snowpack dev --verbose --reload ``` To test a local version of the CLI tool use ```bash node /path/to/snowpack-repository/create-snowpack-app/cli [my-new-dir] --template @snowpack/app-template-vue ``` To test a local version of the `create-snowpack-app` templates use ```bash npx create-snowpack-app [my-new-dir] --template ./path/to/template ``` Note the path must start with must start with a `.` to be considered local The `--verbose` flag enables additional logs which will help to identify the source of a problem. The `--reload` will clear the local cache which might have been created by a different `snowpack` version. Learn more about [Snowpack's CLI flags](/reference/cli-command-line-interface). ## Documentation The [Snowpack website](https://snowpack.dev) source is located in [`/www`](./www). The documentation source files are located in [`/docs`](./docs) for contributor convenience. The directories inside of `/docs` are automatically copied to `/www/_template`, so relative URLs in the documentation use `/www` as their base path. ### Localization If you’d like to help localize, we’d love your help! This is our proposed structure for [`/docs`](./docs): - English translations in the root (e.g. `/docs/tutorials/getting-started.md` is English) - All other languages located in `/docs/[ISO 639-1]/` folders, mirroring structure (e.g. `/docs/tutorials/es/getting-started.md`). Some other considerations: - The `.md` filename should match its English name, only to mark it as the same document. Within the document itself, the title shown to users will be localized. - We’re open to having locale-specific guides that don’t exist in English, but please [start a discussion][discussion] before doing so, so we understand the context. ## Pull Request Guidelines Checkout a topic branch from a base branch, e.g. `main`, and merge back against that branch. If adding a feature, it probably should have been brought up in a [discussion][discussion] instead before the PR was created. Some tips for creating your first pull request: - Provide background for why a PR was created. - Link to any relevant issues, discussions, or past PRs. - Add accompanying tests if applicable. - Ensure all tests have been passed. - Be sure to add a [changesets][changesets] message along with your commit! Our friendly 🤖 bot will help guide you with more instructions. ## Discussion [Join the Pika Discord][discord] [changesets]: https://github.com/atlassian/changesets [discord]: https://discord.gg/rS8SnRk [discussion]: https://github.com/withastro/snowpack/discussions ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2019 Fred K. Schott Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ > **Update (April 20, 2022):** Snowpack is no longer actively maintained and is not recommended for new projects. > > Check out [Vite](https://vitejs.dev/) for a well-maintained Snowpack alternative. > See also: [esbuild](https://esbuild.github.io/), [parcel](https://parceljs.org/)

Snowpack

Snowpack is a lightning-fast frontend build tool, designed to leverage JavaScript's native module system (known as ESM). It is an alternative to heavier, more complex bundlers like webpack or Parcel in your development workflow. ### Key Features - Develop faster, with a dev server that starts up in **50ms or less.** - See changes reflected [instantly in the browser.](https://www.snowpack.dev/concepts/hot-module-replacement) - Integrate your favorite bundler for a [production-optimized build.](https://www.snowpack.dev/concepts/build-pipeline) - Enjoy out-of-the-box support for [TypeScript, JSX, CSS Modules and more.](https://www.snowpack.dev/reference/supported-files) - Connect your favorite tools with [third-party plugins.](https://www.snowpack.dev/plugins) **💁 More info at the official [Snowpack website ➞](https://snowpack.dev)**
> **Contributor Guidelines:** [CONTRIBUTING.md](./CONTRIBUTING.md) > **License:** [MIT](https://github.com/withastro/snowpack/blob/main/LICENSE) ================================================ FILE: create-snowpack-app/README.md ================================================ ## 👉 [`create-snowpack-app`](./cli) ================================================ FILE: create-snowpack-app/app-scripts-lit-element/package.json ================================================ { "name": "@snowpack/app-scripts-lit-element", "version": "2.0.0", "main": "snowpack.config.js", "publishConfig": { "access": "public" }, "license": "MIT", "homepage": "https://github.com/withastro/snowpack/tree/main/create-snowpack-app/app-scripts-lit-element#readme", "repository": { "type": "git", "url": "https://github.com/withastro/snowpack.git", "directory": "create-snowpack-app/app-scripts-lit-element" }, "dependencies": { "@babel/core": "^7.14.0", "@snowpack/plugin-babel": "^2.1.7", "@snowpack/plugin-dotenv": "^2.1.0" }, "gitHead": "a01616bb0787d56cd782f94cecf2daa12c7594e4" } ================================================ FILE: create-snowpack-app/app-scripts-lit-element/snowpack.config.js ================================================ const fs = require('fs'); const path = require('path'); const url = require('url'); const isTS = fs.existsSync(url.pathToFileURL(path.join(process.cwd(), 'tsconfig.json'))); module.exports = { mount: { public: {url: '/', static: true}, src: {url: '/dist'}, }, plugins: ['@snowpack/plugin-babel', '@snowpack/plugin-dotenv'], devOptions: {}, packageOptions: {}, }; ================================================ FILE: create-snowpack-app/app-scripts-lit-element/tsconfig.base.json ================================================ { "compilerOptions": { "module": "esnext", "target": "esnext", "moduleResolution": "node", "jsx": "preserve", "baseUrl": "./", "allowSyntheticDefaultImports": true, "importsNotUsedAsValues": "error", /* more strict checking for errors that per-file transpilers like `esbuild` would crash */ "isolatedModules": true, /* noEmit - We only use TypeScript for type checking. */ "noEmit": true, /* Additional Options */ "strict": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, "resolveJsonModule": true } } ================================================ FILE: create-snowpack-app/app-scripts-preact/babel.config.json ================================================ { "presets": [ [ "@babel/preset-react", { "pragma": "h", "pragmaFrag": "Fragment" } ], [ "@babel/preset-typescript", { "jsxPragma": "h" } ] ], "env": { "development": { "plugins": ["@prefresh/babel-plugin"] } } } ================================================ FILE: create-snowpack-app/app-scripts-preact/jest/babelTransform.js ================================================ // @remove-file-on-eject /** * Copyright (c) 2014-present, Facebook, Inc. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ ('use strict'); const babelJest = require('babel-jest'); module.exports = babelJest.createTransformer({ presets: [ [ '@babel/preset-env', { targets: { node: 'current', }, }, ], [ '@babel/preset-react', { pragma: 'h', pragmaFrag: 'Fragment', }, ], [ '@babel/preset-typescript', { jsxPragma: 'h', }, ], ], }); ================================================ FILE: create-snowpack-app/app-scripts-preact/jest/cssTransform.js ================================================ // @remove-on-eject-begin /** * Copyright (c) 2014-present, Facebook, Inc. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ // @remove-on-eject-end 'use strict'; // This is a custom Jest transformer turning style imports into empty objects. // http://facebook.github.io/jest/docs/en/webpack.html module.exports = { process() { return 'module.exports = {};'; }, getCacheKey() { // The output is always the same. return 'cssTransform'; }, }; ================================================ FILE: create-snowpack-app/app-scripts-preact/jest/fileTransform.js ================================================ 'use strict'; const path = require('path'); module.exports = { process(src, filename) { const assetFilename = path.basename(filename); return `module.exports = { __esModule: true, default: ${JSON.stringify(assetFilename)}, };`; }, }; ================================================ FILE: create-snowpack-app/app-scripts-preact/jest.config.js ================================================ const fs = require('fs'); const path = require('path'); // Use this instead of `paths.testsSetup` to avoid putting // an absolute filename into configuration after ejecting. // const setupTestsFile = fs.existsSync(paths.testsSetup) // ? `/src/setupTests.js` // : undefined; const setupTestsFile = true; module.exports = function () { return { verbose: true, setupFiles: [require.resolve('react-app-polyfill/jsdom')], setupFilesAfterEnv: setupTestsFile ? ['/jest.setup.js'] : [], testMatch: [ '/src/**/__tests__/**/*.{js,jsx,ts,tsx}', '/src/**/*.{spec,test}.{js,jsx,ts,tsx}', ], transform: { '^.+\\.(js|jsx|ts|tsx)$': path.resolve(__dirname, 'jest/babelTransform.js'), '^.+\\.css$': path.resolve(__dirname, 'jest/cssTransform.js'), '^(?!.*\\.(js|jsx|ts|tsx|css|json)$)': path.resolve(__dirname, 'jest/fileTransform.js'), }, transformIgnorePatterns: ['node_modules'], // transformIgnorePatterns: [ // "[/\\\\]node_modules[/\\\\].+\\.(js|jsx|ts|tsx)$", // "^.+\\.module\\.(css|sass|scss)$", // ], }; }; ================================================ FILE: create-snowpack-app/app-scripts-preact/package.json ================================================ { "name": "@snowpack/app-scripts-preact", "version": "2.0.1", "main": "snowpack.config.js", "publishConfig": { "access": "public" }, "license": "MIT", "homepage": "https://github.com/withastro/snowpack/tree/main/create-snowpack-app/app-scripts-preact#readme", "repository": { "type": "git", "url": "https://github.com/withastro/snowpack.git", "directory": "create-snowpack-app/app-scripts-preact" }, "dependencies": { "@babel/core": "^7.14.0", "@babel/preset-env": "^7.14.0", "@babel/preset-react": "^7.13.13", "@babel/preset-typescript": "^7.13.0", "@prefresh/babel-plugin": "^0.4.1", "@prefresh/snowpack": "^3.1.2", "@snowpack/plugin-babel": "^2.1.7", "@snowpack/plugin-dotenv": "^2.1.0", "babel-jest": "^26.6.3", "react-app-polyfill": "^2.0.0" }, "gitHead": "a01616bb0787d56cd782f94cecf2daa12c7594e4" } ================================================ FILE: create-snowpack-app/app-scripts-preact/snowpack.config.js ================================================ const fs = require('fs'); const path = require('path'); const cwd = process.cwd(); const isTS = fs.existsSync(path.join(cwd, 'tsconfig.json')); module.exports = { mount: { public: {url: '/', static: true}, src: {url: '/dist'}, }, plugins: ['@snowpack/plugin-babel', '@prefresh/snowpack', '@snowpack/plugin-dotenv'], packageOptions: {}, }; ================================================ FILE: create-snowpack-app/app-scripts-preact/tsconfig.base.json ================================================ { "compilerOptions": { "module": "esnext", "target": "esnext", "moduleResolution": "node", "jsx": "preserve", "jsxFactory": "h", "baseUrl": "./", "allowSyntheticDefaultImports": true, "importsNotUsedAsValues": "error", /* more strict checking for errors that per-file transpilers like `esbuild` would crash */ "isolatedModules": true, /* noEmit - We only use TypeScript for type checking. */ "noEmit": true, /* Additional Options */ "strict": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, "resolveJsonModule": true } } ================================================ FILE: create-snowpack-app/app-scripts-react/babel.config.json ================================================ { "presets": [["@babel/preset-react"], "@babel/preset-typescript"] } ================================================ FILE: create-snowpack-app/app-scripts-react/jest/babelTransform.js ================================================ // @remove-file-on-eject /** * Copyright (c) 2014-present, Facebook, Inc. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ 'use strict'; const importMetaBabelPlugin = require('./importMetaBabelPlugin'); const babelJestModule = require('babel-jest'); const babelJest = babelJestModule.__esModule ? babelJestModule.default : babelJestModule; module.exports = babelJest.createTransformer({ presets: ['babel-preset-react-app', '@babel/preset-react', '@babel/preset-typescript'], plugins: [[importMetaBabelPlugin]], }); ================================================ FILE: create-snowpack-app/app-scripts-react/jest/cssTransform.js ================================================ // @remove-on-eject-begin /** * Copyright (c) 2014-present, Facebook, Inc. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ // @remove-on-eject-end 'use strict'; // This is a custom Jest transformer turning style imports into empty objects. // http://facebook.github.io/jest/docs/en/webpack.html module.exports = { process() { return 'module.exports = {};'; }, getCacheKey() { // The output is always the same. return 'cssTransform'; }, }; ================================================ FILE: create-snowpack-app/app-scripts-react/jest/esbuildTransform.js ================================================ // NOTE: THIS IS CURRENTLY DISABLED UNTIL ESBUILD SUPPORTS ESM->CJS WITHOUT BUNDLING // SEE: https://github.com/evanw/esbuild/issues/109 'use strict'; const path = require('path'); const execa = require('execa'); const esbuildPath = require.resolve('esbuild'); const esbuildBin = path.resolve(esbuildPath, '..', '..', 'bin', 'esbuild'); module.exports = { process(code, filename) { const result = execa.sync( esbuildBin, [ `--loader=${path.extname(filename).substr(1)}`, '--format=cjs', '--platform=node', '--target=es2019', ], {input: code}, ); return {code: result.stdout}; }, }; ================================================ FILE: create-snowpack-app/app-scripts-react/jest/fileTransform.js ================================================ 'use strict'; const path = require('path'); module.exports = { process(src, filename) { const assetFilename = path.basename(filename); return `module.exports = { __esModule: true, default: ${JSON.stringify(assetFilename)}, };`; }, }; ================================================ FILE: create-snowpack-app/app-scripts-react/jest/importMetaBabelPlugin.js ================================================ 'use strict'; const template = require('@babel/template').default; /** * Add import.meta.env support * Note: import.meta.url is not supported at this time */ module.exports = function importMetaBabelPlugin() { const ast = template.ast(` ({ env: { ...Object.fromEntries( Object.entries(process.env).filter(([k]) => /^SNOWPACK_PUBLIC_/.test(k)), ), MODE: 'test', NODE_ENV: 'test', }, }) `); return { visitor: { MetaProperty(path) { path.replaceWith(ast); }, }, }; }; ================================================ FILE: create-snowpack-app/app-scripts-react/jest.config.js ================================================ const fs = require('fs'); const path = require('path'); // Use this instead of `paths.testsSetup` to avoid putting // an absolute filename into configuration after ejecting. // const setupTestsFile = fs.existsSync(paths.testsSetup) // ? `/src/setupTests.js` // : undefined; const setupTestsFile = true; module.exports = function () { return { verbose: true, setupFiles: [require.resolve('react-app-polyfill/jsdom')], setupFilesAfterEnv: setupTestsFile ? ['/jest.setup.js'] : [], testMatch: [ '/src/**/__tests__/**/*.{js,jsx,ts,tsx}', '/src/**/*.{spec,test}.{js,jsx,ts,tsx}', ], transform: { '^.+\\.(js|jsx|ts|tsx)$': path.resolve(__dirname, 'jest/babelTransform.js'), '^.+\\.css$': path.resolve(__dirname, 'jest/cssTransform.js'), '^(?!.*\\.(js|jsx|ts|tsx|css|json)$)': path.resolve(__dirname, 'jest/fileTransform.js'), }, transformIgnorePatterns: ['node_modules'], testEnvironment: 'jsdom', // transformIgnorePatterns: [ // "[/\\\\]node_modules[/\\\\].+\\.(js|jsx|ts|tsx)$", // "^.+\\.module\\.(css|sass|scss)$", // ], }; }; ================================================ FILE: create-snowpack-app/app-scripts-react/package.json ================================================ { "name": "@snowpack/app-scripts-react", "version": "2.0.1", "main": "snowpack.config.js", "publishConfig": { "access": "public" }, "license": "MIT", "homepage": "https://github.com/withastro/snowpack/tree/main/create-snowpack-app/app-scripts-react#readme", "repository": { "type": "git", "url": "https://github.com/withastro/snowpack.git", "directory": "create-snowpack-app/app-scripts-react" }, "dependencies": { "@babel/core": "^7.14.0", "@babel/preset-react": "^7.13.13", "@babel/preset-typescript": "^7.13.0", "@snowpack/plugin-babel": "^2.1.7", "@snowpack/plugin-dotenv": "^2.1.0", "@snowpack/plugin-react-refresh": "^2.5.0", "@snowpack/plugin-typescript": "^1.2.1", "babel-jest": "^27.2.2", "babel-preset-react-app": "^10.0.0", "react-app-polyfill": "^2.0.0" }, "peerDependencies": { "jest": "^27.2.2" }, "gitHead": "a01616bb0787d56cd782f94cecf2daa12c7594e4" } ================================================ FILE: create-snowpack-app/app-scripts-react/snowpack.config.js ================================================ const fs = require('fs'); const path = require('path'); const cwd = process.cwd(); const isTS = fs.existsSync(path.join(cwd, 'tsconfig.json')); module.exports = { mount: { public: {url: '/', static: true}, src: {url: '/dist'}, }, plugins: [ '@snowpack/plugin-react-refresh', '@snowpack/plugin-babel', '@snowpack/plugin-dotenv', ...(isTS ? ['@snowpack/plugin-typescript'] : []), ], devOptions: {}, packageOptions: {}, }; ================================================ FILE: create-snowpack-app/app-scripts-react/tsconfig.base.json ================================================ { "compilerOptions": { "module": "esnext", "target": "esnext", "moduleResolution": "node", "jsx": "preserve", "baseUrl": "./", "allowSyntheticDefaultImports": true, "importsNotUsedAsValues": "error", /* more strict checking for errors that per-file transpilers like `esbuild` would crash */ "isolatedModules": true, /* noEmit - We only use TypeScript for type checking. */ "noEmit": true, /* Additional Options */ "strict": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, "resolveJsonModule": true } } ================================================ FILE: create-snowpack-app/app-scripts-svelte/jest/babelTransform.js ================================================ // @remove-file-on-eject /** * Copyright (c) 2014-present, Facebook, Inc. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ 'use strict'; const fs = require('fs'); const path = require('path'); const babelJest = require('babel-jest'); const importMetaBabelPlugin = require('./importMetaBabelPlugin'); const userBabelConfig = getUserBabelConfig(); module.exports = babelJest.createTransformer({ presets: [ [ '@babel/preset-env', { targets: { node: 'current', }, }, ], ...(userBabelConfig.presets || []), ], plugins: [[importMetaBabelPlugin]], }); function getUserBabelConfig() { const userBabelConfigLoc = path.join(process.cwd(), 'babel.config.json'); if (fs.existsSync(userBabelConfigLoc)) { return require(userBabelConfigLoc); } return {}; } ================================================ FILE: create-snowpack-app/app-scripts-svelte/jest/importMetaBabelPlugin.js ================================================ 'use strict'; const template = require('@babel/template').default; /** * Add import.meta.env support * Note: import.meta.url is not supported at this time */ module.exports = function importMetaBabelPlugin() { const ast = template.ast(` ({ env: { ...Object.fromEntries( Object.entries(process.env).filter(([k]) => /^SNOWPACK_PUBLIC_/.test(k)), ), MODE: 'test', NODE_ENV: 'test', }, }) `); return { visitor: { MetaProperty(path) { path.replaceWith(ast); }, }, }; }; ================================================ FILE: create-snowpack-app/app-scripts-svelte/jest.config.js ================================================ const fs = require('fs'); const path = require('path'); // Use this instead of `paths.testsSetup` to avoid putting // an absolute filename into configuration after ejecting. // const setupTestsFile = fs.existsSync(paths.testsSetup) // ? `/src/setupTests.js` // : undefined; const setupTestsFile = true; module.exports = function () { const userSvelteConfig = getUserSvelteConfig(); return { testMatch: [ '/src/**/__tests__/**/*.{js,jsx,ts,tsx}', '/src/**/*.{spec,test}.{js,jsx,ts,tsx}', ], transform: { '^.+\\.svelte$': ['jest-transform-svelte', {preprocess: userSvelteConfig.preprocess}], '^.+\\.(js|ts)$': path.resolve(__dirname, 'jest/babelTransform.js'), }, moduleFileExtensions: ['js', 'ts', 'svelte'], testPathIgnorePatterns: ['node_modules'], transformIgnorePatterns: ['node_modules'], bail: false, verbose: true, setupFilesAfterEnv: setupTestsFile ? ['/jest.setup.js'] : [], }; }; function getUserSvelteConfig() { const userSvelteConfigLoc = path.join(process.cwd(), 'svelte.config.js'); if (fs.existsSync(userSvelteConfigLoc)) { return require(userSvelteConfigLoc); } return {}; } ================================================ FILE: create-snowpack-app/app-scripts-svelte/package.json ================================================ { "name": "@snowpack/app-scripts-svelte", "version": "2.0.1", "main": "snowpack.config.js", "publishConfig": { "access": "public" }, "license": "MIT", "homepage": "https://github.com/withastro/snowpack/tree/main/create-snowpack-app/app-scripts-svelte#readme", "repository": { "type": "git", "url": "https://github.com/withastro/snowpack.git", "directory": "create-snowpack-app/app-scripts-svelte" }, "peerDependencies": { "svelte": "^3.21.0" }, "devDependencies": { "svelte": "^3.21.0" }, "dependencies": { "@babel/core": "^7.14.0", "@babel/preset-typescript": "^7.13.0", "@snowpack/plugin-dotenv": "^2.1.0", "@snowpack/plugin-svelte": "^3.6.1", "babel-jest": "^26.6.3", "jest-transform-svelte": "^2.1.1" }, "gitHead": "a01616bb0787d56cd782f94cecf2daa12c7594e4" } ================================================ FILE: create-snowpack-app/app-scripts-svelte/snowpack.config.js ================================================ module.exports = { mount: { public: {url: '/', static: true}, src: {url: '/dist'}, }, plugins: ['@snowpack/plugin-svelte', '@snowpack/plugin-dotenv'], }; ================================================ FILE: create-snowpack-app/app-scripts-vue/package.json ================================================ { "name": "@snowpack/app-scripts-vue", "version": "2.0.0", "main": "snowpack.config.js", "license": "MIT", "homepage": "https://github.com/withastro/snowpack/tree/main/create-snowpack-app/app-scripts-vue#readme", "repository": { "type": "git", "url": "https://github.com/withastro/snowpack.git", "directory": "create-snowpack-app/app-scripts-vue" }, "publishConfig": { "access": "public" }, "dependencies": { "@snowpack/plugin-dotenv": "^2.0.4", "@snowpack/plugin-vue": "^2.2.4" }, "gitHead": "a01616bb0787d56cd782f94cecf2daa12c7594e4" } ================================================ FILE: create-snowpack-app/app-scripts-vue/snowpack.config.js ================================================ module.exports = { mount: { public: {url: '/', static: true}, src: {url: '/dist'}, }, plugins: ['@snowpack/plugin-vue', '@snowpack/plugin-dotenv'], }; ================================================ FILE: create-snowpack-app/app-scripts-vue/tsconfig.base.json ================================================ { "compilerOptions": { "module": "esnext", "target": "es2015", "moduleResolution": "node", "jsx": "preserve", "baseUrl": "./", "allowSyntheticDefaultImports": true, "importsNotUsedAsValues": "error", /* more strict checking for errors that per-file transpilers like `esbuild` would crash */ "isolatedModules": true, /* noEmit - We only use TypeScript for type checking. */ "noEmit": true, /* Additional Options */ "strict": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, "resolveJsonModule": true } } ================================================ FILE: create-snowpack-app/app-template-11ty/.eleventy.js ================================================ module.exports = function (eleventyConfig) { eleventyConfig.setTemplateFormats([ // Templates: 'html', 'njk', 'md', // Static Assets: 'css', 'jpeg', 'jpg', 'png', 'svg', 'woff', 'woff2', ]); eleventyConfig.addPassthroughCopy('static'); return { dir: { input: '_template', includes: '../_includes', output: '_output', }, }; }; ================================================ FILE: create-snowpack-app/app-template-11ty/.npmignore ================================================ .build build ================================================ FILE: create-snowpack-app/app-template-11ty/.prettierrc ================================================ { "singleQuote": true, "trailingComma": "all" } ================================================ FILE: create-snowpack-app/app-template-11ty/README.md ================================================ # New Project > ✨ Bootstrapped with Create Snowpack App (CSA). ## Available Scripts ### npm start Runs the app in the development mode. Open http://localhost:8080 to view it in the browser. The page will reload if you make edits. You will also see any lint errors in the console. ### npm run build Builds a static copy of your site to the `build/` folder. Your app is ready to be deployed! **For the best production performance:** Add a build bundler plugin like [@snowpack/plugin-webpack](https://github.com/withastro/snowpack/tree/main/plugins/plugin-webpack) or [snowpack-plugin-rollup-bundle](https://github.com/ParamagicDev/snowpack-plugin-rollup-bundle) to your `snowpack.config.mjs` config file. ### Q: What about Eject? No eject needed! Snowpack guarantees zero lock-in, and CSA strives for the same. ================================================ FILE: create-snowpack-app/app-template-11ty/_includes/layouts/base.njk ================================================ Snowpack App {{ content | safe }} ================================================ FILE: create-snowpack-app/app-template-11ty/_output/about/index.html ================================================ Snowpack App

About

11ty, powered by Snowpack.


Back to Home

================================================ FILE: create-snowpack-app/app-template-11ty/_output/index.html ================================================ Snowpack App About Page ================================================ FILE: create-snowpack-app/app-template-11ty/_output/static/index.css ================================================ body { background: #222; color: #eee; font-family: Arial, Helvetica, sans-serif; text-align: center; } a { color: #aaa; } .banner { display: flex; justify-content: center; align-items: center; } .banner img, .banner svg { display: block; padding: 1.5rem; } #canvas { display: block; margin: 0rem auto; width: 720px; height: 420px; } ================================================ FILE: create-snowpack-app/app-template-11ty/_template/about.md ================================================ --- layout: layouts/base.njk --- # About [11ty](https://www.11ty.dev/), powered by [Snowpack](http://snowpack.dev/).
[Back to Home](/) ================================================ FILE: create-snowpack-app/app-template-11ty/_template/index.njk ================================================ --- layout: layouts/base.njk --- About Page ================================================ FILE: create-snowpack-app/app-template-11ty/_template/static/index.css ================================================ body { background: #222; color: #eee; font-family: Arial, Helvetica, sans-serif; text-align: center; } a { color: #aaa; } .banner { display: flex; justify-content: center; align-items: center; } .banner img, .banner svg { display: block; padding: 1.5rem; } #canvas { display: block; margin: 0rem auto; width: 720px; height: 420px; } ================================================ FILE: create-snowpack-app/app-template-11ty/_template/static/robots.txt ================================================ # https://www.robotstxt.org/robotstxt.html User-agent: * Disallow: ================================================ FILE: create-snowpack-app/app-template-11ty/package.json ================================================ { "name": "@snowpack/app-template-11ty", "description": "A preconfigured template for Snowpack with Eleventy", "version": "2.0.1", "license": "MIT", "homepage": "https://github.com/withastro/snowpack/tree/main/create-snowpack-app/app-template-11ty#readme", "repository": { "type": "git", "url": "https://github.com/withastro/snowpack.git", "directory": "create-snowpack-app/app-template-11ty" }, "keywords": [ "csa-template" ], "publishConfig": { "access": "public" }, "scripts": { "start": "snowpack dev", "build": "snowpack build", "format": "prettier --write \"src/**/*.js\"", "lint": "prettier --check \"src/**/*.js\"", "test": "echo \"This template does not include a test runner by default.\" && exit 1" }, "dependencies": { "canvas-confetti": "^1.2.0" }, "devDependencies": { "@11ty/eleventy": "^0.11.0", "@snowpack/plugin-run-script": "^2.3.0", "prettier": "^2.2.1", "snowpack": "^3.3.7" }, "gitHead": "a01616bb0787d56cd782f94cecf2daa12c7594e4" } ================================================ FILE: create-snowpack-app/app-template-11ty/snowpack.config.mjs ================================================ /** @type {import("snowpack").SnowpackUserConfig } */ export default { mount: { _output: { url: '/', static: true }, src: { url: '/dist' }, }, plugins: [ ['@snowpack/plugin-run-script', { cmd: 'eleventy', watch: '$1 --watch' }], ], routes: [ /* Enable an SPA Fallback in development: */ // {"match": "routes", "src": ".*", "dest": "/index.html"}, ], optimize: { /* Example: Bundle your final build: */ // "bundle": true, }, packageOptions: { /* ... */ }, devOptions: { // Eleventy updates multiple files at once, so add a 300ms delay before we trigger a browser update hmrDelay: 300, }, buildOptions: { /* ... */ }, }; ================================================ FILE: create-snowpack-app/app-template-11ty/src/index.js ================================================ /** * This file is just a silly example to show everything working in the browser. * When you're ready to start on your site, clear the file. Happy hacking! **/ import confetti from 'canvas-confetti'; confetti.create(document.getElementById('canvas'), { resize: true, useWorker: true, })({ particleCount: 200, spread: 200 }); ================================================ FILE: create-snowpack-app/app-template-blank/.npmignore ================================================ .build build ================================================ FILE: create-snowpack-app/app-template-blank/.prettierrc ================================================ { "singleQuote": true, "trailingComma": "all" } ================================================ FILE: create-snowpack-app/app-template-blank/README.md ================================================ # New Project > ✨ Bootstrapped with Create Snowpack App (CSA). ## Available Scripts ### npm start Runs the app in the development mode. Open http://localhost:8080 to view it in the browser. The page will reload if you make edits. You will also see any lint errors in the console. ### npm run build Builds a static copy of your site to the `build/` folder. Your app is ready to be deployed! **For the best production performance:** Add a build bundler plugin like [@snowpack/plugin-webpack](https://github.com/withastro/snowpack/tree/main/plugins/plugin-webpack) or [snowpack-plugin-rollup-bundle](https://github.com/ParamagicDev/snowpack-plugin-rollup-bundle) to your `snowpack.config.mjs` config file. ### Q: What about Eject? No eject needed! Snowpack guarantees zero lock-in, and CSA strives for the same. ================================================ FILE: create-snowpack-app/app-template-blank/package.json ================================================ { "name": "@snowpack/app-template-blank", "description": "A preconfigured template for Snowpack with Prettier", "version": "2.2.0", "license": "MIT", "homepage": "https://github.com/withastro/snowpack/tree/main/create-snowpack-app/app-template-blank#readme", "repository": { "type": "git", "url": "https://github.com/withastro/snowpack.git", "directory": "create-snowpack-app/app-template-blank" }, "keywords": [ "csa-template" ], "publishConfig": { "access": "public" }, "scripts": { "start": "snowpack dev", "build": "snowpack build", "format": "prettier --write \"src/**/*.js\"", "lint": "prettier --check \"src/**/*.js\"", "test": "echo \"This template does not include a test runner by default.\" && exit 1" }, "dependencies": {}, "devDependencies": { "prettier": "^2.2.1", "snowpack": "^3.3.7" }, "gitHead": "a01616bb0787d56cd782f94cecf2daa12c7594e4" } ================================================ FILE: create-snowpack-app/app-template-blank/public/index.html ================================================ Snowpack App

Page has been open for 0 seconds.

Learn web development

================================================ FILE: create-snowpack-app/app-template-blank/public/robots.txt ================================================ # https://www.robotstxt.org/robotstxt.html User-agent: * Disallow: ================================================ FILE: create-snowpack-app/app-template-blank/snowpack.config.mjs ================================================ /** @type {import("snowpack").SnowpackUserConfig } */ export default { mount: { public: { url: '/', static: true }, src: { url: '/dist' }, }, plugins: [ /* ... */ ], routes: [ /* Enable an SPA Fallback in development: */ // {"match": "routes", "src": ".*", "dest": "/index.html"}, ], optimize: { /* Example: Bundle your final build: */ // "bundle": true, }, packageOptions: { /* ... */ }, devOptions: { /* ... */ }, buildOptions: { /* ... */ }, }; ================================================ FILE: create-snowpack-app/app-template-blank/src/index.css ================================================ body { font-size: calc(10px + 2vmin); font-family: Arial, Helvetica, sans-serif; } #img { display: block; margin: auto; height: 128px; width: 128px; padding: 2rem; } p { display: block; margin: 1rem auto; text-align: center; } #counter { background-color: rgb(46, 94, 130); color: white; padding: 4px 8px; border-radius: 4px; } a { color: rgb(46, 94, 130); } ================================================ FILE: create-snowpack-app/app-template-blank/src/index.js ================================================ const counter = document.querySelector('#counter'); let seconds = 0; setInterval(() => { seconds += 1; counter.textContent = seconds; }, 1000); ================================================ FILE: create-snowpack-app/app-template-blank-typescript/.npmignore ================================================ .build build ================================================ FILE: create-snowpack-app/app-template-blank-typescript/.prettierrc ================================================ { "singleQuote": true, "trailingComma": "all" } ================================================ FILE: create-snowpack-app/app-template-blank-typescript/README.md ================================================ # New Project > ✨ Bootstrapped with Create Snowpack App (CSA). ## Available Scripts ### npm start Runs the app in the development mode. Open http://localhost:8080 to view it in the browser. The page will reload if you make edits. You will also see any lint errors in the console. ### npm run build Builds a static copy of your site to the `build/` folder. Your app is ready to be deployed! **For the best production performance:** Add a build bundler plugin like [@snowpack/plugin-webpack](https://github.com/withastro/snowpack/tree/main/plugins/plugin-webpack) or [snowpack-plugin-rollup-bundle](https://github.com/ParamagicDev/snowpack-plugin-rollup-bundle) to your `snowpack.config.mjs` config file. ### Q: What about Eject? No eject needed! Snowpack guarantees zero lock-in, and CSA strives for the same. ================================================ FILE: create-snowpack-app/app-template-blank-typescript/package.json ================================================ { "name": "@snowpack/app-template-blank-typescript", "description": "A preconfigured template for Snowpack with Typescript", "version": "2.2.0", "license": "MIT", "homepage": "https://github.com/withastro/snowpack/tree/main/create-snowpack-app/app-template-blank-typescript#readme", "repository": { "type": "git", "url": "https://github.com/withastro/snowpack.git", "directory": "create-snowpack-app/app-template-blank-typescript" }, "keywords": [ "csa-template" ], "publishConfig": { "access": "public" }, "scripts": { "start": "snowpack dev", "build": "snowpack build", "format": "prettier --write \"src/**/*.{ts,js}\"", "lint": "prettier --check \"src/**/*.{ts,js}\"", "test": "echo \"This template does not include a test runner by default.\" && exit 1" }, "dependencies": {}, "devDependencies": { "@snowpack/plugin-typescript": "^1.2.1", "@types/snowpack-env": "^2.3.3", "prettier": "^2.2.1", "snowpack": "^3.3.7", "typescript": "^4.3.4" }, "gitHead": "a01616bb0787d56cd782f94cecf2daa12c7594e4" } ================================================ FILE: create-snowpack-app/app-template-blank-typescript/public/index.html ================================================ Snowpack App

Page has been open for 0 seconds.

Learn web development

================================================ FILE: create-snowpack-app/app-template-blank-typescript/public/robots.txt ================================================ # https://www.robotstxt.org/robotstxt.html User-agent: * Disallow: ================================================ FILE: create-snowpack-app/app-template-blank-typescript/snowpack.config.mjs ================================================ /** @type {import("snowpack").SnowpackUserConfig } */ export default { mount: { public: { url: '/', static: true }, src: { url: '/dist' }, }, plugins: [ [ '@snowpack/plugin-typescript', { /* Yarn PnP workaround: see https://www.npmjs.com/package/@snowpack/plugin-typescript */ ...(process.versions.pnp ? { tsc: 'yarn pnpify tsc' } : {}), }, ], ], routes: [ /* Enable an SPA Fallback in development: */ // {"match": "routes", "src": ".*", "dest": "/index.html"}, ], optimize: { /* Example: Bundle your final build: */ // "bundle": true, }, packageOptions: { /* ... */ }, devOptions: { /* ... */ }, buildOptions: { /* ... */ }, }; ================================================ FILE: create-snowpack-app/app-template-blank-typescript/src/index.css ================================================ body { font-size: calc(10px + 2vmin); font-family: Arial, Helvetica, sans-serif; } #img { display: block; margin: auto; height: 128px; width: 128px; padding: 2rem; } p { display: block; margin: 1rem auto; text-align: center; } #counter { background-color: rgb(46, 94, 130); color: white; padding: 4px 8px; border-radius: 4px; } a { color: rgb(46, 94, 130); } ================================================ FILE: create-snowpack-app/app-template-blank-typescript/src/index.ts ================================================ const counter = document.querySelector('#counter') as HTMLSpanElement; let seconds = 0; setInterval(() => { seconds += 1; counter.textContent = seconds.toString(); }, 1000); export {}; ================================================ FILE: create-snowpack-app/app-template-blank-typescript/tsconfig.json ================================================ { "include": ["src", "types"], "compilerOptions": { "module": "esnext", "target": "esnext", "moduleResolution": "node", "jsx": "preserve", "baseUrl": "./", /* paths - import rewriting/resolving */ "paths": { // If you configured any Snowpack aliases, add them here. // Add this line to get types for streaming imports (packageOptions.source="remote"): // "*": [".snowpack/types/*"] // More info: https://www.snowpack.dev/guides/streaming-imports }, "allowSyntheticDefaultImports": true, "importsNotUsedAsValues": "error", /* more strict checking for errors that per-file transpilers like `esbuild` would crash */ "isolatedModules": true, /* noEmit - We only use TypeScript for type checking. */ "noEmit": true, /* Additional Options */ "strict": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, "resolveJsonModule": true, "useDefineForClassFields": true } } ================================================ FILE: create-snowpack-app/app-template-blank-typescript/types/static.d.ts ================================================ /* Use this file to declare any custom file extensions for importing */ /* Use this folder to also add/extend a package d.ts file, if needed. */ /* CSS MODULES */ declare module '*.module.css' { const classes: { [key: string]: string }; export default classes; } declare module '*.module.scss' { const classes: { [key: string]: string }; export default classes; } declare module '*.module.sass' { const classes: { [key: string]: string }; export default classes; } declare module '*.module.less' { const classes: { [key: string]: string }; export default classes; } declare module '*.module.styl' { const classes: { [key: string]: string }; export default classes; } /* CSS */ declare module '*.css'; declare module '*.scss'; declare module '*.sass'; declare module '*.less'; declare module '*.styl'; /* IMAGES */ declare module '*.svg' { const ref: string; export default ref; } declare module '*.bmp' { const ref: string; export default ref; } declare module '*.gif' { const ref: string; export default ref; } declare module '*.jpg' { const ref: string; export default ref; } declare module '*.jpeg' { const ref: string; export default ref; } declare module '*.png' { const ref: string; export default ref; } /* CUSTOM: ADD YOUR OWN HERE */ ================================================ FILE: create-snowpack-app/app-template-lit-element/.npmignore ================================================ .build build ================================================ FILE: create-snowpack-app/app-template-lit-element/.prettierrc ================================================ { "singleQuote": true, "trailingComma": "all" } ================================================ FILE: create-snowpack-app/app-template-lit-element/README.md ================================================ # New Project > ✨ Bootstrapped with Create Snowpack App (CSA). ## Available Scripts ### npm start Runs the app in the development mode. Open http://localhost:8080 to view it in the browser. The page will reload if you make edits. You will also see any lint errors in the console. ### npm run build Builds the app for production to the `dist/` folder. It correctly bundles the app in production mode and optimizes the build for the best performance. ## Directives In case you need to add a directive like `classMap` you should add the extension to the import: ``` import { classMap } from "lit-html/directives/class-map.js"; ``` ================================================ FILE: create-snowpack-app/app-template-lit-element/babel.config.json ================================================ { "plugins": [ ["@babel/plugin-proposal-decorators", { "decoratorsBeforeExport": true }], ["@babel/plugin-proposal-class-properties", { "loose": true }] ] } ================================================ FILE: create-snowpack-app/app-template-lit-element/package.json ================================================ { "name": "@snowpack/app-template-lit-element", "description": "A preconfigured template for Snowpack with LitElement", "version": "2.1.2", "license": "MIT", "homepage": "https://github.com/withastro/snowpack/tree/main/create-snowpack-app/app-template-lit-element#readme", "repository": { "type": "git", "url": "https://github.com/withastro/snowpack.git", "directory": "create-snowpack-app/app-template-lit-element" }, "keywords": [ "csa-template" ], "publishConfig": { "access": "public" }, "scripts": { "start": "snowpack dev", "build": "snowpack build", "format": "prettier --write \"src/**/*.js\"", "lint": "prettier --check \"src/**/*.js\"", "test": "echo \"This template does not include a test runner by default.\" && exit 1" }, "dependencies": { "lit-element": "^2.4.0", "lit-html": "^1.4.0" }, "devDependencies": { "@babel/plugin-proposal-class-properties": "^7.13.0", "@babel/plugin-proposal-decorators": "^7.13.15", "@snowpack/plugin-babel": "^2.1.7", "@snowpack/plugin-dotenv": "^2.1.0", "prettier": "^2.2.1", "snowpack": "^3.3.7" }, "gitHead": "a01616bb0787d56cd782f94cecf2daa12c7594e4" } ================================================ FILE: create-snowpack-app/app-template-lit-element/public/index.css ================================================ body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; color: white; margin: 0; padding: 0; } ================================================ FILE: create-snowpack-app/app-template-lit-element/public/index.html ================================================ Snowpack App ================================================ FILE: create-snowpack-app/app-template-lit-element/public/robots.txt ================================================ # https://www.robotstxt.org/robotstxt.html User-agent: * Disallow: ================================================ FILE: create-snowpack-app/app-template-lit-element/snowpack.config.mjs ================================================ /** @type {import("snowpack").SnowpackUserConfig } */ export default { mount: { public: { url: '/', static: true }, src: { url: '/dist' }, }, plugins: ['@snowpack/plugin-babel', '@snowpack/plugin-dotenv'], routes: [ /* Enable an SPA Fallback in development: */ // {"match": "routes", "src": ".*", "dest": "/index.html"}, ], optimize: { /* Example: Bundle your final build: */ // "bundle": true, }, packageOptions: { /* ... */ }, devOptions: { /* ... */ }, buildOptions: { /* ... */ }, }; ================================================ FILE: create-snowpack-app/app-template-lit-element/src/app-root.js ================================================ import { customElement, property, LitElement, html, css } from 'lit-element'; @customElement('app-root') export class AppRoot extends LitElement { @property() message = 'Learn LitElement'; static get styles() { return css` h1 { font-size: 4rem; } .wrapper { display: flex; justify-content: center; align-items: center; flex-direction: column; height: 100vh; background-color: #2196f3; background: linear-gradient(315deg, #b4d2ea 0%, #2196f3 100%); font-size: 24px; } .link { color: white; } `; } render() { return html`

LitElement + Snowpack

Edit src/app-root.js and save to reload.

${this.message}
`; } } ================================================ FILE: create-snowpack-app/app-template-lit-element/src/index.js ================================================ import './app-root'; ================================================ FILE: create-snowpack-app/app-template-lit-element-typescript/.npmignore ================================================ .build build ================================================ FILE: create-snowpack-app/app-template-lit-element-typescript/.prettierrc ================================================ { "singleQuote": true, "trailingComma": "all" } ================================================ FILE: create-snowpack-app/app-template-lit-element-typescript/README.md ================================================ # New Project > ✨ Bootstrapped with Create Snowpack App (CSA). ## Available Scripts ### npm start Runs the app in the development mode. Open http://localhost:8080 to view it in the browser. The page will reload if you make edits. You will also see any lint errors in the console. ### npm run build Builds the app for production to the `dist/` folder. It correctly bundles the app in production mode and optimizes the build for the best performance. ## Directives In case you need to add a directive like `classMap` you should add the extension to the import: ``` import { classMap } from "lit-html/directives/class-map.js"; ``` ================================================ FILE: create-snowpack-app/app-template-lit-element-typescript/babel.config.json ================================================ { "presets": ["@babel/preset-typescript"], "plugins": [ ["@babel/plugin-proposal-decorators", { "decoratorsBeforeExport": true }], ["@babel/plugin-proposal-class-properties", { "loose": true }] ] } ================================================ FILE: create-snowpack-app/app-template-lit-element-typescript/package.json ================================================ { "name": "@snowpack/app-template-lit-element-typescript", "description": "A preconfigured template for Snowpack with TypeScript and LitElement", "version": "2.1.3", "license": "MIT", "homepage": "https://github.com/withastro/snowpack/tree/main/create-snowpack-app/app-template-lit-element-typescript#readme", "repository": { "type": "git", "url": "https://github.com/withastro/snowpack.git", "directory": "create-snowpack-app/app-template-lit-element-typescript" }, "keywords": [ "csa-template" ], "publishConfig": { "access": "public" }, "scripts": { "start": "snowpack dev", "build": "snowpack build", "format": "prettier --write \"src/**/*.ts\"", "lint": "prettier --check \"src/**/*.ts\"", "test": "echo \"This template does not include a test runner by default.\" && exit 1" }, "dependencies": { "lit-element": "^2.4.0", "lit-html": "^1.4.0" }, "devDependencies": { "@babel/plugin-proposal-class-properties": "^7.13.0", "@babel/plugin-proposal-decorators": "^7.13.15", "@babel/preset-typescript": "^7.13.0", "@snowpack/plugin-babel": "^2.1.7", "@snowpack/plugin-dotenv": "^2.1.0", "@snowpack/plugin-typescript": "^1.2.1", "@types/snowpack-env": "^2.3.3", "prettier": "^2.2.1", "snowpack": "^3.3.7", "typescript": "^4.3.4" }, "gitHead": "a01616bb0787d56cd782f94cecf2daa12c7594e4" } ================================================ FILE: create-snowpack-app/app-template-lit-element-typescript/public/index.css ================================================ body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; color: white; margin: 0; padding: 0; } ================================================ FILE: create-snowpack-app/app-template-lit-element-typescript/public/index.html ================================================ Snowpack App ================================================ FILE: create-snowpack-app/app-template-lit-element-typescript/public/robots.txt ================================================ # https://www.robotstxt.org/robotstxt.html User-agent: * Disallow: ================================================ FILE: create-snowpack-app/app-template-lit-element-typescript/snowpack.config.mjs ================================================ /** @type {import("snowpack").SnowpackUserConfig } */ export default { mount: { public: { url: '/', static: true }, src: { url: '/dist' }, }, plugins: [ '@snowpack/plugin-babel', '@snowpack/plugin-dotenv', [ '@snowpack/plugin-typescript', { /* Yarn PnP workaround: see https://www.npmjs.com/package/@snowpack/plugin-typescript */ ...(process.versions.pnp ? { tsc: 'yarn pnpify tsc' } : {}), }, ], ], routes: [ /* Enable an SPA Fallback in development: */ // {"match": "routes", "src": ".*", "dest": "/index.html"}, ], optimize: { /* Example: Bundle your final build: */ // "bundle": true, }, packageOptions: { /* ... */ }, devOptions: { /* ... */ }, buildOptions: { /* ... */ }, }; ================================================ FILE: create-snowpack-app/app-template-lit-element-typescript/src/app-root.ts ================================================ import { customElement, property, LitElement, html, css } from 'lit-element'; @customElement('app-root') export class AppRoot extends LitElement { @property() message = 'Learn LitElement'; static get styles() { return css` h1 { font-size: 4rem; } .wrapper { display: flex; justify-content: center; align-items: center; flex-direction: column; height: 100vh; background-color: #2196f3; background: linear-gradient(315deg, #b4d2ea 0%, #2196f3 100%); font-size: 24px; } .link { color: white; } `; } render() { return html`

LitElement + Snowpack

Edit src/app-root.ts and save to reload.

${this.message}
`; } } ================================================ FILE: create-snowpack-app/app-template-lit-element-typescript/src/index.ts ================================================ import './app-root'; ================================================ FILE: create-snowpack-app/app-template-lit-element-typescript/tsconfig.json ================================================ { "include": ["src", "types"], "compilerOptions": { "module": "esnext", "target": "esnext", "moduleResolution": "node", "jsx": "preserve", "baseUrl": "./", /* paths - import rewriting/resolving */ "paths": { // If you configured any Snowpack aliases, add them here. // Add this line to get types for streaming imports (packageOptions.source="remote"): // "*": [".snowpack/types/*"] // More info: https://www.snowpack.dev/guides/streaming-imports }, /* noEmit - Snowpack builds (emits) files, not tsc. */ "noEmit": true, /* LitElement - Add decorator support! */ "experimentalDecorators": true, "emitDecoratorMetadata": true, /* Additional Options */ "strict": true, "skipLibCheck": true, "types": ["snowpack-env"], "forceConsistentCasingInFileNames": true, "resolveJsonModule": true, "allowSyntheticDefaultImports": true, "importsNotUsedAsValues": "error" } } ================================================ FILE: create-snowpack-app/app-template-lit-element-typescript/types/static.d.ts ================================================ /* Use this file to declare any custom file extensions for importing */ /* Use this folder to also add/extend a package d.ts file, if needed. */ /* CSS MODULES */ declare module '*.module.css' { const classes: { [key: string]: string }; export default classes; } declare module '*.module.scss' { const classes: { [key: string]: string }; export default classes; } declare module '*.module.sass' { const classes: { [key: string]: string }; export default classes; } declare module '*.module.less' { const classes: { [key: string]: string }; export default classes; } declare module '*.module.styl' { const classes: { [key: string]: string }; export default classes; } /* CSS */ declare module '*.css'; declare module '*.scss'; declare module '*.sass'; declare module '*.less'; declare module '*.styl'; /* IMAGES */ declare module '*.svg' { const ref: string; export default ref; } declare module '*.bmp' { const ref: string; export default ref; } declare module '*.gif' { const ref: string; export default ref; } declare module '*.jpg' { const ref: string; export default ref; } declare module '*.jpeg' { const ref: string; export default ref; } declare module '*.png' { const ref: string; export default ref; } /* CUSTOM: ADD YOUR OWN HERE */ ================================================ FILE: create-snowpack-app/app-template-minimal/README.md ================================================ # New Project > ✨ Bootstrapped with Create Snowpack App (CSA). ## Available Scripts ### npm start Runs the app in the development mode. Open http://localhost:8080 to view it in the browser. The page will reload if you make edits. You will also see any lint errors in the console. ### npm run build Builds a static copy of your site to the `build/` folder. Your app is ready to be deployed! **For the best production performance:** Add a build bundler plugin like [@snowpack/plugin-webpack](https://github.com/withastro/snowpack/tree/main/plugins/plugin-webpack) or [snowpack-plugin-rollup-bundle](https://github.com/ParamagicDev/snowpack-plugin-rollup-bundle) to your `snowpack.config.mjs` config file. ### Q: What about Eject? No eject needed! Snowpack guarantees zero lock-in, and CSA strives for the same. ================================================ FILE: create-snowpack-app/app-template-minimal/index.css ================================================ /* Add CSS styles here! */ body { font-family: sans-serif; } ================================================ FILE: create-snowpack-app/app-template-minimal/index.html ================================================ Starter Snowpack App

Welcome to Snowpack!

================================================ FILE: create-snowpack-app/app-template-minimal/index.js ================================================ /* Add JavaScript code here! */ console.log('Hello World! You did it! Welcome to Snowpack :D'); ================================================ FILE: create-snowpack-app/app-template-minimal/package.json ================================================ { "name": "@snowpack/app-template-minimal", "description": "A preconfigured minimal template for Snowpack", "version": "2.1.1", "license": "MIT", "homepage": "https://github.com/withastro/snowpack/tree/main/create-snowpack-app/app-template-preact#readme", "repository": { "type": "git", "url": "https://github.com/withastro/snowpack.git", "directory": "create-snowpack-app/app-template-minimal" }, "keywords": [ "csa-template" ], "main": "index.js", "scripts": { "start": "snowpack dev", "build": "snowpack build", "test": "echo \"This template does not include a test runner by default.\" && exit 1" }, "devDependencies": { "snowpack": "^3.3.7" } } ================================================ FILE: create-snowpack-app/app-template-minimal/snowpack.config.mjs ================================================ /** @type {import("snowpack").SnowpackUserConfig } */ export default { mount: { /* ... */ }, plugins: [ /* ... */ ], routes: [ /* Enable an SPA Fallback in development: */ // {"match": "routes", "src": ".*", "dest": "/index.html"}, ], optimize: { /* Example: Bundle your final build: */ // "bundle": true, }, packageOptions: { /* ... */ }, devOptions: { /* ... */ }, buildOptions: { /* ... */ }, }; ================================================ FILE: create-snowpack-app/app-template-preact/.npmignore ================================================ .build build ================================================ FILE: create-snowpack-app/app-template-preact/.prettierrc ================================================ { "singleQuote": true, "trailingComma": "all" } ================================================ FILE: create-snowpack-app/app-template-preact/README.md ================================================ # New Project > ✨ Bootstrapped with Create Snowpack App (CSA). ## Available Scripts ### npm start Runs the app in the development mode. Open http://localhost:8080 to view it in the browser. The page will reload if you make edits. You will also see any lint errors in the console. ### npm test Launches the test runner in the interactive watch mode. See the section about running tests for more information. ### npm run build Builds the app for production to the `build/` folder. It correctly bundles Preact in production mode and optimizes the build for the best performance. ### Q: What about Eject? No eject needed! Snowpack guarantees zero lock-in, and CSA strives for the same. ================================================ FILE: create-snowpack-app/app-template-preact/package.json ================================================ { "name": "@snowpack/app-template-preact", "description": "A preconfigured template for Snowpack with Preact", "version": "2.1.2", "license": "MIT", "homepage": "https://github.com/withastro/snowpack/tree/main/create-snowpack-app/app-template-preact#readme", "repository": { "type": "git", "url": "https://github.com/withastro/snowpack.git", "directory": "create-snowpack-app/app-template-preact" }, "keywords": [ "csa-template" ], "publishConfig": { "access": "public" }, "scripts": { "start": "snowpack dev", "build": "snowpack build", "format": "prettier --write \"src/**/*.{js,jsx}\"", "lint": "prettier --check \"src/**/*.{js,jsx}\"", "test": "web-test-runner \"src/**/*.test.jsx\"" }, "dependencies": { "preact": "^10.5.13" }, "devDependencies": { "@prefresh/snowpack": "^3.0.0", "@snowpack/plugin-dotenv": "^2.1.0", "@snowpack/web-test-runner-plugin": "^0.2.2", "@testing-library/preact": "^2.0.1", "@web/test-runner": "^0.13.3", "chai": "^4.3.4", "prettier": "^2.2.1", "snowpack": "^3.8.0" }, "gitHead": "a01616bb0787d56cd782f94cecf2daa12c7594e4" } ================================================ FILE: create-snowpack-app/app-template-preact/public/index.html ================================================ Snowpack App
================================================ FILE: create-snowpack-app/app-template-preact/public/robots.txt ================================================ # https://www.robotstxt.org/robotstxt.html User-agent: * Disallow: ================================================ FILE: create-snowpack-app/app-template-preact/snowpack.config.mjs ================================================ /** @type {import("snowpack").SnowpackUserConfig } */ export default { mount: { public: { url: '/', static: true }, src: { url: '/dist' }, }, plugins: ['@snowpack/plugin-dotenv', '@prefresh/snowpack'], routes: [ /* Enable an SPA Fallback in development: */ // {"match": "routes", "src": ".*", "dest": "/index.html"}, ], optimize: { /* Example: Bundle your final build: */ // "bundle": true, }, packageOptions: { /* ... */ }, devOptions: { /* ... */ }, buildOptions: { /* ... */ }, alias: { /* ... */ }, }; ================================================ FILE: create-snowpack-app/app-template-preact/src/App.css ================================================ .App { text-align: center; } .App code { background: #FFF3; padding: 4px 8px; border-radius: 4px; } .App p { margin: 0.4rem; } .App-header { background-color: #282c34; min-height: 100vh; display: flex; flex-direction: column; align-items: center; justify-content: center; font-size: calc(10px + 2vmin); color: white; } .App-link { color: #61dafb; } .App-logo { height: 36vmin; pointer-events: none; margin-bottom: 3rem; animation: App-logo-spin infinite 1.6s ease-in-out alternate; } @keyframes App-logo-spin { from { transform: scale(1); } to { transform: scale(1.06); } } ================================================ FILE: create-snowpack-app/app-template-preact/src/App.jsx ================================================ import { h } from 'preact'; import { useState, useEffect } from 'preact/hooks'; import logo from './logo.png'; import './App.css'; function App() { // Create the count state. const [count, setCount] = useState(0); // Create the counter (+1 every second). useEffect(() => { const timer = setTimeout(() => setCount(count + 1), 1000); return () => clearTimeout(timer); }, [count, setCount]); // Return the App component. return (
logo

Edit src/App.jsx and save to reload.

Page has been open for {count} seconds.

Learn Preact

); } export default App; ================================================ FILE: create-snowpack-app/app-template-preact/src/App.test.jsx ================================================ import { h } from 'preact'; import { render } from '@testing-library/preact'; import { expect } from 'chai'; import App from './App'; describe('', () => { it('renders learn react link', () => { const { getByText } = render(); const linkElement = getByText(/learn preact/i); expect(document.body.contains(linkElement)); }); }); ================================================ FILE: create-snowpack-app/app-template-preact/src/index.css ================================================ body { margin: 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } code { font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace; } ================================================ FILE: create-snowpack-app/app-template-preact/src/index.jsx ================================================ import { h, render } from 'preact'; import 'preact/devtools'; import App from './App.js'; import './index.css'; render(, document.getElementById('root')); ================================================ FILE: create-snowpack-app/app-template-preact/web-test-runner.config.js ================================================ process.env.NODE_ENV = 'test'; module.exports = { plugins: [require('@snowpack/web-test-runner-plugin')()], }; ================================================ FILE: create-snowpack-app/app-template-preact-typescript/.npmignore ================================================ .build build ================================================ FILE: create-snowpack-app/app-template-preact-typescript/.prettierrc ================================================ { "singleQuote": true, "trailingComma": "all" } ================================================ FILE: create-snowpack-app/app-template-preact-typescript/README.md ================================================ # New Project > ✨ Bootstrapped with Create Snowpack App (CSA). ## Available Scripts ### npm start Runs the app in the development mode. Open http://localhost:8080 to view it in the browser. The page will reload if you make edits. You will also see any lint errors in the console. ### npm test Launches the test runner in the interactive watch mode. See the section about running tests for more information. ### npm run build Builds the app for production to the `build/` folder. It correctly bundles Preact in production mode and optimizes the build for the best performance. ### Q: What about Eject? No eject needed! Snowpack guarantees zero lock-in, and CSA strives for the same. ================================================ FILE: create-snowpack-app/app-template-preact-typescript/package.json ================================================ { "name": "@snowpack/app-template-preact-typescript", "description": "A preconfigured template for Snowpack with Preact and Typescript", "version": "2.1.3", "license": "MIT", "homepage": "https://github.com/withastro/snowpack/tree/main/create-snowpack-app/app-template-preact-typescript#readme", "repository": { "type": "git", "url": "https://github.com/withastro/snowpack.git", "directory": "create-snowpack-app/app-template-preact-typescript" }, "keywords": [ "csa-template" ], "publishConfig": { "access": "public" }, "scripts": { "start": "snowpack dev", "build": "snowpack build", "format": "prettier --write \"src/**/*.{js,jsx}\"", "lint": "prettier --check \"src/**/*.{js,jsx}\"", "test": "web-test-runner \"src/**/*.test.tsx\"" }, "dependencies": { "preact": "^10.5.13" }, "devDependencies": { "@prefresh/snowpack": "^3.0.0", "@snowpack/plugin-dotenv": "^2.1.0", "@snowpack/plugin-typescript": "^1.2.1", "@snowpack/web-test-runner-plugin": "^0.2.2", "@testing-library/preact": "^2.0.1", "@types/chai": "^4.2.17", "@types/mocha": "^8.2.2", "@web/test-runner": "^0.13.3", "chai": "^4.3.4", "prettier": "^2.2.1", "snowpack": "^3.8.0", "typescript": "^4.3.4" }, "gitHead": "a01616bb0787d56cd782f94cecf2daa12c7594e4" } ================================================ FILE: create-snowpack-app/app-template-preact-typescript/public/index.html ================================================ Snowpack App
================================================ FILE: create-snowpack-app/app-template-preact-typescript/public/robots.txt ================================================ # https://www.robotstxt.org/robotstxt.html User-agent: * Disallow: ================================================ FILE: create-snowpack-app/app-template-preact-typescript/snowpack.config.mjs ================================================ /** @type {import("snowpack").SnowpackUserConfig } */ export default { mount: { public: { url: '/', static: true }, src: { url: '/dist' }, }, plugins: [ '@prefresh/snowpack', '@snowpack/plugin-dotenv', [ '@snowpack/plugin-typescript', { /* Yarn PnP workaround: see https://www.npmjs.com/package/@snowpack/plugin-typescript */ ...(process.versions.pnp ? { tsc: 'yarn pnpify tsc' } : {}), }, ], ], routes: [ /* Enable an SPA Fallback in development: */ // {"match": "routes", "src": ".*", "dest": "/index.html"}, ], optimize: { /* Example: Bundle your final build: */ // "bundle": true, }, packageOptions: { /* ... */ }, devOptions: { /* ... */ }, buildOptions: { /* ... */ }, }; ================================================ FILE: create-snowpack-app/app-template-preact-typescript/src/App.css ================================================ .App { text-align: center; } .App code { background: #FFF3; padding: 4px 8px; border-radius: 4px; } .App p { margin: 0.4rem; } .App-header { background-color: #282c34; min-height: 100vh; display: flex; flex-direction: column; align-items: center; justify-content: center; font-size: calc(10px + 2vmin); color: white; } .App-link { color: #61dafb; } .App-logo { height: 36vmin; pointer-events: none; margin-bottom: 3rem; animation: App-logo-spin infinite 1.6s ease-in-out alternate; } @keyframes App-logo-spin { from { transform: scale(1); } to { transform: scale(1.06); } } ================================================ FILE: create-snowpack-app/app-template-preact-typescript/src/App.test.tsx ================================================ import { h } from 'preact'; import { render } from '@testing-library/preact'; import { expect } from 'chai'; import App from './App'; describe('', () => { it('renders learn react link', () => { const { getByText } = render(); const linkElement = getByText(/learn preact/i); expect(document.body.contains(linkElement)); }); }); ================================================ FILE: create-snowpack-app/app-template-preact-typescript/src/App.tsx ================================================ import { h } from 'preact'; import { useState, useEffect } from 'preact/hooks'; import logo from './logo.png'; import './App.css'; function App() { // Create the count state. const [count, setCount] = useState(0); // Create the counter (+1 every second). useEffect(() => { const timer = setTimeout(() => setCount(count + 1), 1000); return () => clearTimeout(timer); }, [count, setCount]); // Return the App component. return (
logo

Edit src/App.jsx and save to reload.

Page has been open for {count} seconds.

Learn Preact

); } export default App; ================================================ FILE: create-snowpack-app/app-template-preact-typescript/src/index.css ================================================ body { margin: 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } code { font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace; } ================================================ FILE: create-snowpack-app/app-template-preact-typescript/src/index.tsx ================================================ import { h, render } from 'preact'; import 'preact/devtools'; import App from './App.js'; import './index.css'; const root = document.getElementById('root') if (root) { render(, root); } ================================================ FILE: create-snowpack-app/app-template-preact-typescript/tsconfig.json ================================================ { "include": ["src", "types"], "compilerOptions": { "module": "esnext", "target": "esnext", "moduleResolution": "node", "jsx": "preserve", "jsxFactory": "h", "baseUrl": "./", /* paths - import rewriting/resolving */ "paths": { // If you configured any Snowpack aliases, add them here. // Add this line to get types for streaming imports (packageOptions.source="remote"): // "*": [".snowpack/types/*"] // More info: https://www.snowpack.dev/guides/streaming-imports }, /* noEmit - Snowpack builds (emits) files, not tsc. */ "noEmit": true, /* Additional Options */ "strict": true, "skipLibCheck": true, "types": ["mocha", "snowpack-env"], "forceConsistentCasingInFileNames": true, "resolveJsonModule": true, "allowSyntheticDefaultImports": true, "importsNotUsedAsValues": "error" } } ================================================ FILE: create-snowpack-app/app-template-preact-typescript/types/static.d.ts ================================================ /* Use this file to declare any custom file extensions for importing */ /* Use this folder to also add/extend a package d.ts file, if needed. */ /* CSS MODULES */ declare module '*.module.css' { const classes: { [key: string]: string }; export default classes; } declare module '*.module.scss' { const classes: { [key: string]: string }; export default classes; } declare module '*.module.sass' { const classes: { [key: string]: string }; export default classes; } declare module '*.module.less' { const classes: { [key: string]: string }; export default classes; } declare module '*.module.styl' { const classes: { [key: string]: string }; export default classes; } /* CSS */ declare module '*.css'; declare module '*.scss'; declare module '*.sass'; declare module '*.less'; declare module '*.styl'; /* IMAGES */ declare module '*.svg' { const ref: string; export default ref; } declare module '*.bmp' { const ref: string; export default ref; } declare module '*.gif' { const ref: string; export default ref; } declare module '*.jpg' { const ref: string; export default ref; } declare module '*.jpeg' { const ref: string; export default ref; } declare module '*.png' { const ref: string; export default ref; } /* CUSTOM: ADD YOUR OWN HERE */ ================================================ FILE: create-snowpack-app/app-template-preact-typescript/web-test-runner.config.js ================================================ process.env.NODE_ENV = 'test'; module.exports = { plugins: [require('@snowpack/web-test-runner-plugin')()], }; ================================================ FILE: create-snowpack-app/app-template-react/.npmignore ================================================ .build build ================================================ FILE: create-snowpack-app/app-template-react/.prettierrc ================================================ { "singleQuote": true, "trailingComma": "all" } ================================================ FILE: create-snowpack-app/app-template-react/README.md ================================================ # New Project > ✨ Bootstrapped with Create Snowpack App (CSA). ## Available Scripts ### npm start Runs the app in the development mode. Open http://localhost:8080 to view it in the browser. The page will reload if you make edits. You will also see any lint errors in the console. ### npm run build Builds a static copy of your site to the `build/` folder. Your app is ready to be deployed! **For the best production performance:** Add a build bundler plugin like "@snowpack/plugin-webpack" to your `snowpack.config.mjs` config file. ### npm test Launches the application test runner. Run with the `--watch` flag (`npm test -- --watch`) to run in interactive watch mode. ================================================ FILE: create-snowpack-app/app-template-react/package.json ================================================ { "name": "@snowpack/app-template-react", "description": "A preconfigured template for Snowpack with React", "version": "2.1.2", "license": "MIT", "homepage": "https://github.com/withastro/snowpack/tree/main/create-snowpack-app/app-template-react#readme", "repository": { "type": "git", "url": "https://github.com/withastro/snowpack.git", "directory": "create-snowpack-app/app-template-react" }, "keywords": [ "csa-template" ], "publishConfig": { "access": "public" }, "scripts": { "start": "snowpack dev", "build": "snowpack build", "format": "prettier --write \"src/**/*.{js,jsx}\"", "lint": "prettier --check \"src/**/*.{js,jsx}\"", "test": "web-test-runner \"src/**/*.test.jsx\"" }, "dependencies": { "react": "^17.0.2", "react-dom": "^17.0.2" }, "devDependencies": { "@snowpack/plugin-dotenv": "^2.1.0", "@snowpack/plugin-react-refresh": "^2.5.0", "@snowpack/web-test-runner-plugin": "^0.2.2", "@testing-library/react": "^11.2.6", "@web/test-runner": "^0.13.3", "chai": "^4.3.4", "prettier": "^2.2.1", "snowpack": "^3.8.0" }, "gitHead": "a01616bb0787d56cd782f94cecf2daa12c7594e4" } ================================================ FILE: create-snowpack-app/app-template-react/public/index.html ================================================ Snowpack App
================================================ FILE: create-snowpack-app/app-template-react/public/robots.txt ================================================ # https://www.robotstxt.org/robotstxt.html User-agent: * Disallow: ================================================ FILE: create-snowpack-app/app-template-react/snowpack.config.mjs ================================================ /** @type {import("snowpack").SnowpackUserConfig } */ export default { mount: { public: { url: '/', static: true }, src: { url: '/dist' }, }, plugins: ['@snowpack/plugin-react-refresh', '@snowpack/plugin-dotenv'], routes: [ /* Enable an SPA Fallback in development: */ // {"match": "routes", "src": ".*", "dest": "/index.html"}, ], optimize: { /* Example: Bundle your final build: */ // "bundle": true, }, packageOptions: { /* ... */ }, devOptions: { /* ... */ }, buildOptions: { /* ... */ }, }; ================================================ FILE: create-snowpack-app/app-template-react/src/App.css ================================================ .App { text-align: center; } .App code { background: #FFF3; padding: 4px 8px; border-radius: 4px; } .App p { margin: 0.4rem; } .App-logo { height: 40vmin; pointer-events: none; } @media (prefers-reduced-motion: no-preference) { .App-logo { animation: App-logo-spin infinite 20s linear; } } .App-header { background-color: #282c34; min-height: 100vh; display: flex; flex-direction: column; align-items: center; justify-content: center; font-size: calc(10px + 2vmin); color: white; } .App-link { color: #61dafb; } @keyframes App-logo-spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } } ================================================ FILE: create-snowpack-app/app-template-react/src/App.jsx ================================================ import React, { useState, useEffect } from 'react'; import logo from './logo.svg'; import './App.css'; function App() { // Create the count state. const [count, setCount] = useState(0); // Create the counter (+1 every second). useEffect(() => { const timer = setTimeout(() => setCount(count + 1), 1000); return () => clearTimeout(timer); }, [count, setCount]); // Return the App component. return (
logo

Edit src/App.jsx and save to reload.

Page has been open for {count} seconds.

Learn React

); } export default App; ================================================ FILE: create-snowpack-app/app-template-react/src/App.test.jsx ================================================ import * as React from 'react'; import { render } from '@testing-library/react'; import { expect } from 'chai'; import App from './App'; describe('', () => { it('renders learn react link', () => { const { getByText } = render(); const linkElement = getByText(/learn react/i); expect(document.body.contains(linkElement)); }); }); ================================================ FILE: create-snowpack-app/app-template-react/src/index.css ================================================ body { margin: 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } code { font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace; } ================================================ FILE: create-snowpack-app/app-template-react/src/index.jsx ================================================ import React from 'react'; import ReactDOM from 'react-dom'; import App from './App.jsx'; import './index.css'; ReactDOM.render( , document.getElementById('root'), ); // Hot Module Replacement (HMR) - Remove this snippet to remove HMR. // Learn more: https://www.snowpack.dev/concepts/hot-module-replacement if (import.meta.hot) { import.meta.hot.accept(); } ================================================ FILE: create-snowpack-app/app-template-react/web-test-runner.config.js ================================================ process.env.NODE_ENV = 'test'; module.exports = { plugins: [require('@snowpack/web-test-runner-plugin')()], }; ================================================ FILE: create-snowpack-app/app-template-react-typescript/.npmignore ================================================ .build build ================================================ FILE: create-snowpack-app/app-template-react-typescript/.prettierrc ================================================ { "singleQuote": true, "trailingComma": "all" } ================================================ FILE: create-snowpack-app/app-template-react-typescript/README.md ================================================ # New Project > ✨ Bootstrapped with Create Snowpack App (CSA). ## Available Scripts ### npm start Runs the app in the development mode. Open http://localhost:8080 to view it in the browser. The page will reload if you make edits. You will also see any lint errors in the console. ### npm run build Builds a static copy of your site to the `build/` folder. Your app is ready to be deployed! **For the best production performance:** Add a build bundler plugin like "@snowpack/plugin-webpack" to your `snowpack.config.mjs` config file. ### npm test Launches the application test runner. Run with the `--watch` flag (`npm test -- --watch`) to run in interactive watch mode. ================================================ FILE: create-snowpack-app/app-template-react-typescript/package.json ================================================ { "name": "@snowpack/app-template-react-typescript", "description": "A preconfigured template for Snowpack with React and TypeScript", "version": "2.1.3", "license": "MIT", "homepage": "https://github.com/withastro/snowpack/tree/main/create-snowpack-app/app-template-react-typescript#readme", "repository": { "type": "git", "url": "https://github.com/withastro/snowpack.git", "directory": "create-snowpack-app/app-template-react-typescript" }, "keywords": [ "csa-template" ], "publishConfig": { "access": "public" }, "scripts": { "start": "snowpack dev", "build": "snowpack build", "format": "prettier --write \"src/**/*.{js,jsx,ts,tsx}\"", "lint": "prettier --check \"src/**/*.{js,jsx,ts,tsx}\"", "test": "web-test-runner \"src/**/*.test.tsx\"" }, "dependencies": { "react": "^17.0.2", "react-dom": "^17.0.2" }, "devDependencies": { "@snowpack/plugin-dotenv": "^2.1.0", "@snowpack/plugin-react-refresh": "^2.5.0", "@snowpack/plugin-typescript": "^1.2.1", "@snowpack/web-test-runner-plugin": "^0.2.2", "@testing-library/react": "^11.2.6", "@types/chai": "^4.2.17", "@types/mocha": "^8.2.2", "@types/react": "^17.0.4", "@types/react-dom": "^17.0.3", "@types/snowpack-env": "^2.3.3", "@web/test-runner": "^0.13.3", "chai": "^4.3.4", "prettier": "^2.2.1", "snowpack": "^3.8.0", "typescript": "^4.3.4" }, "gitHead": "a01616bb0787d56cd782f94cecf2daa12c7594e4" } ================================================ FILE: create-snowpack-app/app-template-react-typescript/public/index.html ================================================ Snowpack App
================================================ FILE: create-snowpack-app/app-template-react-typescript/public/robots.txt ================================================ # https://www.robotstxt.org/robotstxt.html User-agent: * Disallow: ================================================ FILE: create-snowpack-app/app-template-react-typescript/snowpack.config.mjs ================================================ /** @type {import("snowpack").SnowpackUserConfig } */ export default { mount: { public: { url: '/', static: true }, src: { url: '/dist' }, }, plugins: [ '@snowpack/plugin-react-refresh', '@snowpack/plugin-dotenv', [ '@snowpack/plugin-typescript', { /* Yarn PnP workaround: see https://www.npmjs.com/package/@snowpack/plugin-typescript */ ...(process.versions.pnp ? { tsc: 'yarn pnpify tsc' } : {}), }, ], ], routes: [ /* Enable an SPA Fallback in development: */ // {"match": "routes", "src": ".*", "dest": "/index.html"}, ], optimize: { /* Example: Bundle your final build: */ // "bundle": true, }, packageOptions: { /* ... */ }, devOptions: { /* ... */ }, buildOptions: { /* ... */ }, }; ================================================ FILE: create-snowpack-app/app-template-react-typescript/src/App.css ================================================ .App { text-align: center; } .App code { background: #FFF3; padding: 4px 8px; border-radius: 4px; } .App p { margin: 0.4rem; } .App-logo { height: 40vmin; pointer-events: none; } @media (prefers-reduced-motion: no-preference) { .App-logo { animation: App-logo-spin infinite 20s linear; } } .App-header { background-color: #282c34; min-height: 100vh; display: flex; flex-direction: column; align-items: center; justify-content: center; font-size: calc(10px + 2vmin); color: white; } .App-link { color: #61dafb; } @keyframes App-logo-spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } } ================================================ FILE: create-snowpack-app/app-template-react-typescript/src/App.test.tsx ================================================ import * as React from 'react'; import { render } from '@testing-library/react'; import { expect } from 'chai'; import App from './App'; describe('', () => { it('renders learn react link', () => { const { getByText } = render(); const linkElement = getByText(/learn react/i); expect(document.body.contains(linkElement)); }); }); ================================================ FILE: create-snowpack-app/app-template-react-typescript/src/App.tsx ================================================ import React, { useState, useEffect } from 'react'; import logo from './logo.svg'; import './App.css'; interface AppProps {} function App({}: AppProps) { // Create the count state. const [count, setCount] = useState(0); // Create the counter (+1 every second). useEffect(() => { const timer = setTimeout(() => setCount(count + 1), 1000); return () => clearTimeout(timer); }, [count, setCount]); // Return the App component. return (
logo

Edit src/App.tsx and save to reload.

Page has been open for {count} seconds.

Learn React

); } export default App; ================================================ FILE: create-snowpack-app/app-template-react-typescript/src/index.css ================================================ body { margin: 0; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } code { font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", monospace; } ================================================ FILE: create-snowpack-app/app-template-react-typescript/src/index.tsx ================================================ import React from 'react'; import ReactDOM from 'react-dom'; import App from './App'; import './index.css'; ReactDOM.render( , document.getElementById('root'), ); // Hot Module Replacement (HMR) - Remove this snippet to remove HMR. // Learn more: https://snowpack.dev/concepts/hot-module-replacement if (import.meta.hot) { import.meta.hot.accept(); } ================================================ FILE: create-snowpack-app/app-template-react-typescript/tsconfig.json ================================================ { "include": ["src", "types"], "compilerOptions": { "module": "esnext", "target": "esnext", "moduleResolution": "node", "jsx": "preserve", "baseUrl": "./", /* paths - import rewriting/resolving */ "paths": { // If you configured any Snowpack aliases, add them here. // Add this line to get types for streaming imports (packageOptions.source="remote"): // "*": [".snowpack/types/*"] // More info: https://www.snowpack.dev/guides/streaming-imports }, /* noEmit - Snowpack builds (emits) files, not tsc. */ "noEmit": true, /* Additional Options */ "strict": true, "skipLibCheck": true, "types": ["mocha", "snowpack-env"], "forceConsistentCasingInFileNames": true, "resolveJsonModule": true, "allowSyntheticDefaultImports": true, "importsNotUsedAsValues": "error" } } ================================================ FILE: create-snowpack-app/app-template-react-typescript/types/static.d.ts ================================================ /* Use this file to declare any custom file extensions for importing */ /* Use this folder to also add/extend a package d.ts file, if needed. */ /* CSS MODULES */ declare module '*.module.css' { const classes: { [key: string]: string }; export default classes; } declare module '*.module.scss' { const classes: { [key: string]: string }; export default classes; } declare module '*.module.sass' { const classes: { [key: string]: string }; export default classes; } declare module '*.module.less' { const classes: { [key: string]: string }; export default classes; } declare module '*.module.styl' { const classes: { [key: string]: string }; export default classes; } /* CSS */ declare module '*.css'; declare module '*.scss'; declare module '*.sass'; declare module '*.less'; declare module '*.styl'; /* IMAGES */ declare module '*.svg' { const ref: string; export default ref; } declare module '*.bmp' { const ref: string; export default ref; } declare module '*.gif' { const ref: string; export default ref; } declare module '*.jpg' { const ref: string; export default ref; } declare module '*.jpeg' { const ref: string; export default ref; } declare module '*.png' { const ref: string; export default ref; } /* CUSTOM: ADD YOUR OWN HERE */ ================================================ FILE: create-snowpack-app/app-template-react-typescript/web-test-runner.config.js ================================================ process.env.NODE_ENV = 'test'; module.exports = { plugins: [require('@snowpack/web-test-runner-plugin')()], }; ================================================ FILE: create-snowpack-app/app-template-svelte/.npmignore ================================================ .build build ================================================ FILE: create-snowpack-app/app-template-svelte/README.md ================================================ # New Project > ✨ Bootstrapped with Create Snowpack App (CSA). ## Available Scripts ### npm start Runs the app in the development mode. Open http://localhost:8080 to view it in the browser. The page will reload if you make edits. You will also see any lint errors in the console. ### npm test Launches the test runner in the interactive watch mode. See the section about running tests for more information. ### npm run build Builds a static copy of your site to the `build/` folder. Your app is ready to be deployed! **For the best production performance:** Add a build bundler plugin like [@snowpack/plugin-webpack](https://github.com/withastro/snowpack/tree/main/plugins/plugin-webpack) or [snowpack-plugin-rollup-bundle](https://github.com/ParamagicDev/snowpack-plugin-rollup-bundle) to your `snowpack.config.mjs` config file. ### Q: What about Eject? No eject needed! Snowpack guarantees zero lock-in, and CSA strives for the same. ================================================ FILE: create-snowpack-app/app-template-svelte/package.json ================================================ { "name": "@snowpack/app-template-svelte", "description": "A preconfigured template for Snowpack with Svelte", "version": "2.1.2", "license": "MIT", "homepage": "https://github.com/withastro/snowpack/tree/main/create-snowpack-app/app-template-svelte#readme", "repository": { "type": "git", "url": "https://github.com/withastro/snowpack.git", "directory": "create-snowpack-app/app-template-svelte" }, "keywords": [ "csa-template" ], "publishConfig": { "access": "public" }, "scripts": { "start": "snowpack dev", "build": "snowpack build", "test": "web-test-runner \"src/**/*.test.js\"" }, "dependencies": { "svelte": "^3.37.0" }, "devDependencies": { "@snowpack/plugin-dotenv": "^2.1.0", "@snowpack/plugin-svelte": "^3.6.1", "@snowpack/web-test-runner-plugin": "^0.2.2", "@testing-library/svelte": "^3.0.3", "@web/test-runner": "^0.13.3", "chai": "^4.3.4", "snowpack": "^3.8.0" }, "gitHead": "a01616bb0787d56cd782f94cecf2daa12c7594e4" } ================================================ FILE: create-snowpack-app/app-template-svelte/public/index.html ================================================ Snowpack App ================================================ FILE: create-snowpack-app/app-template-svelte/public/robots.txt ================================================ # https://www.robotstxt.org/robotstxt.html User-agent: * Disallow: ================================================ FILE: create-snowpack-app/app-template-svelte/snowpack.config.mjs ================================================ /** @type {import("snowpack").SnowpackUserConfig } */ export default { mount: { public: {url: '/', static: true}, src: {url: '/dist'}, }, plugins: ['@snowpack/plugin-svelte', '@snowpack/plugin-dotenv'], routes: [ /* Example: Enable an SPA Fallback in development: */ // {"match": "routes", "src": ".*", "dest": "/index.html"}, ], optimize: { /* Example: Bundle your final build: */ // "bundle": true, }, packageOptions: { /* ... */ }, devOptions: { /* ... */ }, buildOptions: { /* ... */ }, }; ================================================ FILE: create-snowpack-app/app-template-svelte/src/App.svelte ================================================

Edit src/App.svelte and save to reload.

Page has been open for {count} seconds.

Learn Svelte

================================================ FILE: create-snowpack-app/app-template-svelte/src/App.test.js ================================================ import {render} from '@testing-library/svelte'; import {expect} from 'chai'; import App from './App.svelte'; describe('', () => { it('renders learn svelte link', () => { const {getByText} = render(App); const linkElement = getByText(/learn svelte/i); expect(document.body.contains(linkElement)); }); }); ================================================ FILE: create-snowpack-app/app-template-svelte/src/index.js ================================================ import App from './App.svelte'; let app = new App({ target: document.body, }); export default app; // Hot Module Replacement (HMR) - Remove this snippet to remove HMR. // Learn more: https://www.snowpack.dev/concepts/hot-module-replacement if (import.meta.hot) { import.meta.hot.accept(); import.meta.hot.dispose(() => { app.$destroy(); }); } ================================================ FILE: create-snowpack-app/app-template-svelte/web-test-runner.config.js ================================================ process.env.NODE_ENV = 'test'; module.exports = { plugins: [require('@snowpack/web-test-runner-plugin')()], }; ================================================ FILE: create-snowpack-app/app-template-svelte-typescript/.npmignore ================================================ .build build ================================================ FILE: create-snowpack-app/app-template-svelte-typescript/CHANGELOG.md ================================================ # @snowpack/app-template-svelte-typescript ## 2.1.5 ### Patch Changes - fed2c940: Remove default language option as its use is discouraged ================================================ FILE: create-snowpack-app/app-template-svelte-typescript/README.md ================================================ # New Project > ✨ Bootstrapped with Create Snowpack App (CSA). ## Available Scripts ### npm start Runs the app in the development mode. Open http://localhost:8080 to view it in the browser. The page will reload if you make edits. You will also see any lint errors in the console. ### npm test Launches the test runner in the interactive watch mode. See the section about running tests for more information. ### npm run build Builds a static copy of your site to the `build/` folder. Your app is ready to be deployed! **For the best production performance:** Add a build bundler plugin like [@snowpack/plugin-webpack](https://github.com/withastro/snowpack/tree/main/plugins/plugin-webpack) or [snowpack-plugin-rollup-bundle](https://github.com/ParamagicDev/snowpack-plugin-rollup-bundle) to your `snowpack.config.mjs` config file. ### Q: What about Eject? No eject needed! Snowpack guarantees zero lock-in, and CSA strives for the same. ================================================ FILE: create-snowpack-app/app-template-svelte-typescript/package.json ================================================ { "name": "@snowpack/app-template-svelte-typescript", "description": "A preconfigured template for Snowpack with Svelte and TypeScript", "version": "2.1.5", "license": "MIT", "homepage": "https://github.com/withastro/snowpack/tree/main/create-snowpack-app/app-template-svelte-typescript#readme", "repository": { "type": "git", "url": "https://github.com/withastro/snowpack.git", "directory": "create-snowpack-app/app-template-svelte-typescript" }, "keywords": [ "csa-template" ], "publishConfig": { "access": "public" }, "scripts": { "start": "snowpack dev", "build": "snowpack build", "test": "web-test-runner \"src/**/*.test.ts\"" }, "dependencies": { "svelte": "^3.37.0" }, "devDependencies": { "@snowpack/plugin-dotenv": "^2.2.0", "@snowpack/plugin-svelte": "^3.6.1", "@snowpack/plugin-typescript": "^1.2.1", "@snowpack/web-test-runner-plugin": "^0.2.2", "@testing-library/svelte": "^3.0.3", "@tsconfig/svelte": "^1.0.10", "@types/chai": "^4.2.17", "@types/mocha": "^8.2.2", "@types/snowpack-env": "^2.3.3", "@web/test-runner": "^0.13.3", "chai": "^4.3.4", "snowpack": "^3.8.8", "svelte-preprocess": "^4.7.2", "typescript": "^4.3.4" }, "gitHead": "a01616bb0787d56cd782f94cecf2daa12c7594e4" } ================================================ FILE: create-snowpack-app/app-template-svelte-typescript/public/index.html ================================================ Snowpack App ================================================ FILE: create-snowpack-app/app-template-svelte-typescript/public/robots.txt ================================================ # https://www.robotstxt.org/robotstxt.html User-agent: * Disallow: ================================================ FILE: create-snowpack-app/app-template-svelte-typescript/snowpack.config.mjs ================================================ /** @type {import("snowpack").SnowpackUserConfig } */ export default { mount: { public: {url: '/', static: true}, src: {url: '/dist'}, }, plugins: [ '@snowpack/plugin-svelte', '@snowpack/plugin-dotenv', [ '@snowpack/plugin-typescript', { /* Yarn PnP workaround: see https://www.npmjs.com/package/@snowpack/plugin-typescript */ ...(process.versions.pnp ? {tsc: 'yarn pnpify tsc'} : {}), }, ], ], routes: [ /* Enable an SPA Fallback in development: */ // {"match": "routes", "src": ".*", "dest": "/index.html"}, ], optimize: { /* Example: Bundle your final build: */ // "bundle": true, }, packageOptions: { /* ... */ }, devOptions: { /* ... */ }, buildOptions: { /* ... */ }, }; ================================================ FILE: create-snowpack-app/app-template-svelte-typescript/src/App.svelte ================================================

Edit src/App.svelte and save to reload.

Page has been open for {count} seconds.

Learn Svelte

================================================ FILE: create-snowpack-app/app-template-svelte-typescript/src/App.test.ts ================================================ import {render} from '@testing-library/svelte'; import {expect} from 'chai'; import App from './App.svelte'; describe('', () => { it('renders learn svelte link', () => { const {getByText} = render(App); const linkElement = getByText(/learn svelte/i); expect(document.body.contains(linkElement)); }); }); ================================================ FILE: create-snowpack-app/app-template-svelte-typescript/src/index.ts ================================================ import App from './App.svelte'; var app = new App({ target: document.body, }); export default app; // Hot Module Replacement (HMR) - Remove this snippet to remove HMR. // Learn more: https://www.snowpack.dev/concepts/hot-module-replacement if (import.meta.hot) { import.meta.hot.accept(); import.meta.hot.dispose(() => { app.$destroy(); }); } ================================================ FILE: create-snowpack-app/app-template-svelte-typescript/svelte.config.js ================================================ const autoPreprocess = require('svelte-preprocess'); module.exports = { preprocess: autoPreprocess(), }; ================================================ FILE: create-snowpack-app/app-template-svelte-typescript/tsconfig.json ================================================ { "extends": "@tsconfig/svelte/tsconfig.json", "include": ["src", "types"], "compilerOptions": { "module": "esnext", "moduleResolution": "node", "jsx": "preserve", "baseUrl": "./", /* paths - import rewriting/resolving */ "paths": { // If you configured any Snowpack aliases, add them here. // Add this line to get types for streaming imports (packageOptions.source="remote"): // "*": [".snowpack/types/*"] // More info: https://www.snowpack.dev/guides/streaming-imports }, /* noEmit - Snowpack builds (emits) files, not tsc. */ "noEmit": true, /* Additional Options */ "strict": true, "skipLibCheck": true, "types": ["mocha", "snowpack-env"], "forceConsistentCasingInFileNames": true, "resolveJsonModule": true, "useDefineForClassFields": true, "allowSyntheticDefaultImports": true, "importsNotUsedAsValues": "error" } } ================================================ FILE: create-snowpack-app/app-template-svelte-typescript/types/static.d.ts ================================================ /* Use this file to declare any custom file extensions for importing */ /* Use this folder to also add/extend a package d.ts file, if needed. */ /* CSS MODULES */ declare module '*.module.css' { const classes: {[key: string]: string}; export default classes; } declare module '*.module.scss' { const classes: {[key: string]: string}; export default classes; } declare module '*.module.sass' { const classes: {[key: string]: string}; export default classes; } declare module '*.module.less' { const classes: {[key: string]: string}; export default classes; } declare module '*.module.styl' { const classes: {[key: string]: string}; export default classes; } /* CSS */ declare module '*.css'; declare module '*.scss'; declare module '*.sass'; declare module '*.less'; declare module '*.styl'; /* IMAGES */ declare module '*.svg' { const ref: string; export default ref; } declare module '*.bmp' { const ref: string; export default ref; } declare module '*.gif' { const ref: string; export default ref; } declare module '*.jpg' { const ref: string; export default ref; } declare module '*.jpeg' { const ref: string; export default ref; } declare module '*.png' { const ref: string; export default ref; } /* CUSTOM: ADD YOUR OWN HERE */ ================================================ FILE: create-snowpack-app/app-template-svelte-typescript/web-test-runner.config.js ================================================ process.env.NODE_ENV = 'test'; module.exports = { plugins: [require('@snowpack/web-test-runner-plugin')()], }; ================================================ FILE: create-snowpack-app/app-template-vue/.npmignore ================================================ .build build ================================================ FILE: create-snowpack-app/app-template-vue/README.md ================================================ # New Project > ✨ Bootstrapped with Create Snowpack App (CSA). ## Available Scripts ### npm start Runs the app in the development mode. Open http://localhost:8080 to view it in the browser. The page will reload if you make edits. You will also see any lint errors in the console. ### npm test **⚠️ NOTE:** Vue 3 testing support is still in progress. This template does not ship with a test runner. ### npm run build Builds the app for production to the `build/` folder. It correctly bundles Vue in production mode and optimizes the build for the best performance. ### Q: What about Eject? No eject needed! Snowpack guarantees zero lock-in, and CSA strives for the same. ================================================ FILE: create-snowpack-app/app-template-vue/package.json ================================================ { "name": "@snowpack/app-template-vue", "description": "A preconfigured template for Snowpack with Vue", "version": "2.1.2", "license": "MIT", "homepage": "https://github.com/withastro/snowpack/tree/main/create-snowpack-app/app-template-vue#readme", "repository": { "type": "git", "url": "https://github.com/withastro/snowpack.git", "directory": "create-snowpack-app/app-template-vue" }, "keywords": [ "csa-template" ], "publishConfig": { "access": "public" }, "scripts": { "start": "snowpack dev", "build": "snowpack build", "test": "echo \"This template does not include a test runner by default.\" && exit 1" }, "dependencies": { "vue": "^3.0.11" }, "devDependencies": { "@snowpack/plugin-dotenv": "^2.1.0", "@snowpack/plugin-vue": "^2.4.0", "snowpack": "^3.3.7" }, "gitHead": "a01616bb0787d56cd782f94cecf2daa12c7594e4" } ================================================ FILE: create-snowpack-app/app-template-vue/public/index.html ================================================ Snowpack App
================================================ FILE: create-snowpack-app/app-template-vue/public/robots.txt ================================================ # https://www.robotstxt.org/robotstxt.html User-agent: * Disallow: ================================================ FILE: create-snowpack-app/app-template-vue/snowpack.config.mjs ================================================ /** @type {import("snowpack").SnowpackUserConfig } */ export default { mount: { public: {url: '/', static: true}, src: {url: '/dist'}, }, plugins: ['@snowpack/plugin-vue', '@snowpack/plugin-dotenv'], routes: [ /* Enable an SPA Fallback in development: */ // {"match": "routes", "src": ".*", "dest": "/index.html"}, ], optimize: { /* Example: Bundle your final build: */ // "bundle": true, }, packageOptions: { /* ... */ }, devOptions: { /* ... */ }, buildOptions: { /* ... */ }, }; ================================================ FILE: create-snowpack-app/app-template-vue/src/App.vue ================================================ ================================================ FILE: create-snowpack-app/app-template-vue/src/index.js ================================================ import {createApp} from 'vue'; import App from './App.vue'; const app = createApp(App); app.mount('#app'); // Hot Module Replacement (HMR) - Remove this snippet to remove HMR. // Learn more: https://www.snowpack.dev/concepts/hot-module-replacement if (import.meta.hot) { import.meta.hot.accept(); import.meta.hot.dispose(() => { app.unmount(); }); } ================================================ FILE: create-snowpack-app/app-template-vue-typescript/.npmignore ================================================ .build build ================================================ FILE: create-snowpack-app/app-template-vue-typescript/README.md ================================================ # New Project > ✨ Bootstrapped with Create Snowpack App (CSA). ## Available Scripts ### npm start Runs the app in the development mode. Open http://localhost:8080 to view it in the browser. The page will reload if you make edits. You will also see any lint errors in the console. ### npm test **⚠️ NOTE:** Vue 3 testing support is still in progress. This template does not ship with a test runner. ### npm run build Builds the app for production to the `build/` folder. It correctly bundles Vue in production mode and optimizes the build for the best performance. ### Q: What about Eject? No eject needed! Snowpack guarantees zero lock-in, and CSA strives for the same. ================================================ FILE: create-snowpack-app/app-template-vue-typescript/package.json ================================================ { "name": "@snowpack/app-template-vue-typescript", "description": "A preconfigured template for Snowpack with Vue and TypeScript", "version": "2.1.2", "license": "MIT", "homepage": "https://github.com/withastro/snowpack/tree/main/create-snowpack-app/app-template-vue-typescript#readme", "repository": { "type": "git", "url": "https://github.com/withastro/snowpack.git", "directory": "create-snowpack-app/app-template-vue-typescript" }, "keywords": [ "csa-template" ], "publishConfig": { "access": "public" }, "scripts": { "start": "snowpack dev", "build": "snowpack build", "type-check": "tsc", "test": "echo \"This template does not include a test runner by default.\" && exit 1" }, "dependencies": { "vue": "^3.0.11" }, "devDependencies": { "@snowpack/plugin-dotenv": "^2.1.0", "@snowpack/plugin-vue": "^2.4.0", "snowpack": "^3.3.7", "typescript": "^4.3.4" }, "gitHead": "a01616bb0787d56cd782f94cecf2daa12c7594e4" } ================================================ FILE: create-snowpack-app/app-template-vue-typescript/public/index.html ================================================ Snowpack App
================================================ FILE: create-snowpack-app/app-template-vue-typescript/public/robots.txt ================================================ # https://www.robotstxt.org/robotstxt.html User-agent: * Disallow: ================================================ FILE: create-snowpack-app/app-template-vue-typescript/snowpack.config.mjs ================================================ /** @type {import("snowpack").SnowpackUserConfig } */ export default { mount: { public: {url: '/', static: true}, src: {url: '/dist'}, }, plugins: [ '@snowpack/plugin-vue', '@snowpack/plugin-vue/plugin-tsx-jsx.js', '@snowpack/plugin-dotenv', ], routes: [ /* Enable an SPA Fallback in development: */ // {"match": "routes", "src": ".*", "dest": "/index.html"}, ], optimize: { /* Example: Bundle your final build: */ // "bundle": true, }, packageOptions: { /* ... */ }, devOptions: { /* ... */ }, buildOptions: { /* ... */ }, }; ================================================ FILE: create-snowpack-app/app-template-vue-typescript/src/App.vue ================================================ ================================================ FILE: create-snowpack-app/app-template-vue-typescript/src/index.js ================================================ import {createApp} from 'vue'; import App from './App.vue'; const app = createApp(App); app.mount('#app'); // Hot Module Replacement (HMR) - Remove this snippet to remove HMR. // Learn more: https://www.snowpack.dev/concepts/hot-module-replacement if (import.meta.hot) { import.meta.hot.accept(); import.meta.hot.dispose(() => { app.unmount(); }); } ================================================ FILE: create-snowpack-app/app-template-vue-typescript/tsconfig.json ================================================ { "include": ["src", "types"], "compilerOptions": { "module": "esnext", "target": "esnext", "moduleResolution": "node", "jsx": "preserve", "baseUrl": "./", /* paths - import rewriting/resolving */ "paths": { // If you configured any Snowpack aliases, add them here. // Add this line to get types for streaming imports (packageOptions.source="remote"): // "*": [".snowpack/types/*"] // More info: https://www.snowpack.dev/guides/streaming-imports }, /* noEmit - Snowpack builds (emits) files, not tsc. */ "noEmit": true, /* Additional Options */ "strict": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, "resolveJsonModule": true, "allowSyntheticDefaultImports": true, "importsNotUsedAsValues": "error" } } ================================================ FILE: create-snowpack-app/app-template-vue-typescript/types/shims-vue.d.ts ================================================ declare module '*.vue' { import {defineComponent} from 'vue'; const component: ReturnType; export default component; } ================================================ FILE: create-snowpack-app/app-template-vue-typescript/types/static.d.ts ================================================ /* Use this file to declare any custom file extensions for importing */ /* Use this folder to also add/extend a package d.ts file, if needed. */ /* CSS MODULES */ declare module '*.module.css' { const classes: {[key: string]: string}; export default classes; } declare module '*.module.scss' { const classes: {[key: string]: string}; export default classes; } declare module '*.module.sass' { const classes: {[key: string]: string}; export default classes; } declare module '*.module.less' { const classes: {[key: string]: string}; export default classes; } declare module '*.module.styl' { const classes: {[key: string]: string}; export default classes; } /* CSS */ declare module '*.css'; declare module '*.scss'; declare module '*.sass'; declare module '*.less'; declare module '*.styl'; /* IMAGES */ declare module '*.svg' { const ref: string; export default ref; } declare module '*.bmp' { const ref: string; export default ref; } declare module '*.gif' { const ref: string; export default ref; } declare module '*.jpg' { const ref: string; export default ref; } declare module '*.jpeg' { const ref: string; export default ref; } declare module '*.png' { const ref: string; export default ref; } /* CUSTOM: ADD YOUR OWN HERE */ ================================================ FILE: create-snowpack-app/cli/README.md ================================================ # Create Snowpack App (CSA) ```sh npx create-snowpack-app new-dir --template @snowpack/app-template-NAME [--use-yarn | --use-pnpm | --no-install | --no-git] ``` ## Official App Templates - [@snowpack/app-template-blank](https://github.com/withastro/snowpack/tree/main/create-snowpack-app/app-template-blank) - [@snowpack/app-template-blank-typescript](https://github.com/withastro/snowpack/tree/main/create-snowpack-app/app-template-blank-typescript) - [@snowpack/app-template-11ty](https://github.com/withastro/snowpack/tree/main/create-snowpack-app/app-template-11ty) - [@snowpack/app-template-lit-element](https://github.com/withastro/snowpack/tree/main/create-snowpack-app/app-template-lit-element) - [@snowpack/app-template-lit-element-typescript](https://github.com/withastro/snowpack/tree/main/create-snowpack-app/app-template-lit-element-typescript) - [@snowpack/app-template-preact](https://github.com/withastro/snowpack/tree/main/create-snowpack-app/app-template-preact) - [@snowpack/app-template-preact-typescript](https://github.com/withastro/snowpack/tree/main/create-snowpack-app/app-template-preact-typescript) - [@snowpack/app-template-react](https://github.com/withastro/snowpack/tree/main/create-snowpack-app/app-template-react) - [@snowpack/app-template-react-typescript](https://github.com/withastro/snowpack/tree/main/create-snowpack-app/app-template-react-typescript) - [@snowpack/app-template-svelte](https://github.com/withastro/snowpack/tree/main/create-snowpack-app/app-template-svelte) - [@snowpack/app-template-svelte-typescript](https://github.com/withastro/snowpack/tree/main/create-snowpack-app/app-template-svelte-typescript) - [@snowpack/app-template-vue](https://github.com/withastro/snowpack/tree/main/create-snowpack-app/app-template-vue) - [@snowpack/app-template-vue-typescript](https://github.com/withastro/snowpack/tree/main/create-snowpack-app/app-template-vue-typescript) ### Featured Community Templates - [snowpack-template-preset-env](https://github.com/argyleink/snowpack-template-preset-env) (PostCSS + Babel) - [11st-Starter-Kit](https://github.com/stefanfrede/11st-starter-kit) (11ty + Snowpack + tailwindcss) - [app-template-rescript-react](https://github.com/jihchi/app-template-rescript-react) (ReScript & rescript-react on top of [@snowpack/app-template-react](https://github.com/withastro/snowpack/tree/main/create-snowpack-app/app-template-react)) - [svelte-tailwind](https://github.com/agneym/svelte-tailwind-snowpack) (Adds PostCSS and TailwindCSS using [svelte-preprocess](https://github.com/sveltejs/svelte-preprocess)) - [snowpack-react-tailwind](https://github.com/mrkldshv/snowpack-react-tailwind) (React + Snowpack + Tailwindcss) - [hyperapp-snowpack](https://github.com/bmartel/hyperapp-snowpack) (Hyperapp + Snowpack + TailwindCSS) - [snowpack-vue-capacitor-2-demo](https://github.com/brodybits/snowpack-vue-capacitor-2-demo) Demo of Snowpack with Vue and [Capacitor mobile app framework](https://capacitorjs.com/) version 2, originally generated from `@snowpack/vue-template` template, see [discussion #905](https://github.com/withastro/snowpack/discussions/905) - [snowpack-react-ssr](https://github.com/matthoffner/snowpack-react-ssr) (React + Server Side Rendering) - [snowpack-app-template-preact-hmr-tailwind](https://github.com/Mozart409/snowpack-app-template-preact-hmr-tailwind) (Snowpack + Preact + HMR + Tailwindcss) - [snowpack-mdx-chakra](https://github.com/molebox/snowpack-mdx)(An opinionated template setup with MDX, Chakra-ui for styling, theme and components, and React Router v6 for routing.) - [app-template-sstt](https://github.com/LBrian/app-template-s2t2) (S2T2 - Snowpack + Svelte + Typescript + TailwindCSS) - [snowpack-svelte-ts-tw](https://github.com/GarrettCannon/snowpack-svelte-ts-tw) (Snowpack + Svelte + Typescript + TailwindCSS) - [snowpack-template-tailwind](https://github.com/jonalvarezz/snowpack-template-tailwind) (Snowpack + TailwindCSS + Autopublish to GitHub Pages) - [glimmer-snowpack](https://github.com/rajasegar/glimmer-snowpack) (Snowpack + [Glimmer.js](https://glimmerjs.com)) - [snowpack-cycle](https://github.com/rajasegar/snowpack-cycle) (A pre-configured Snowpack app template for [Cycle.js](https://cycle.js.org)) - [@snowpack-angular/template](https://github.com/YogliB/snowpack-angular/tree/main/templates/base) (A preconfigured template for Snowpack with [Angular](https://angular.io)) - [snowpack-solid](https://github.com/amoutonbrady/snowpack-solid) (A pre-configured Snowpack app template for [Solid](https://github.com/ryansolid/solid)) - [snowpack-template-ts-rust-wasm](https://github.com/jake-pauls/snowpack-template-ts-rust-wasm) (Snowpack + Typescript + Rust + WebAssembly) - PRs that add a link to this list are welcome! ================================================ FILE: create-snowpack-app/cli/createSnowpackApp.js ================================================ const fs = require('fs'); const path = require('path'); const execa = require('execa'); const yargs = require('yargs-parser'); const {copy, removeSync} = require('fs-extra'); const colors = require('kleur'); const errorAlert = `${colors.red('[ERROR]')}`; const errorLink = `${colors.dim(colors.underline('https://github.com/withastro/snowpack'))}`; function logError(msg) { console.error(`${errorAlert} ${msg} ${errorLink}`); process.exit(1); } function hasPmInstalled(packageManager) { try { execa.commandSync(`${packageManager} --version`); return true; } catch (err) { return false; } } function validateArgs(args) { const {template, useYarn, usePnpm, force, target, install, verbose, git = true, _} = yargs(args); const toInstall = install !== undefined ? install : true; const toInitializeGitRepo = git !== undefined ? git : true; if (useYarn && usePnpm) { logError('You can not use Yarn and pnpm at the same time.'); } if (useYarn && !hasPmInstalled('yarn')) { logError(`Yarn doesn't seem to be installed.`); } if (usePnpm && !hasPmInstalled('pnpm')) { logError(`pnpm doesn't seem to be installed.`); } if (!target && _.length === 2) { logError('Missing --target directory.'); } if (typeof template !== 'string') { logError('Missing --template argument.'); } if (_.length > 3) { logError('Unexpected extra arguments.'); } const targetDirectoryRelative = target || _[2]; const targetDirectory = path.resolve(process.cwd(), targetDirectoryRelative); if (fs.existsSync(targetDirectory) && !force) { logError(`${targetDirectory} already exists. Use \`--force\` to overwrite this directory.`); } return { template, useYarn, usePnpm, targetDirectoryRelative, targetDirectory, toInstall, toInitializeGitRepo, verbose, }; } async function verifyProjectTemplate(isLocalTemplate, {template, dir}) { let keywords; if (isLocalTemplate) { const packageManifest = path.join(dir, 'package.json'); keywords = require(packageManifest).keywords; } else { try { const {stdout} = await execa('npm', ['info', template, 'keywords', '--json']); keywords = JSON.parse(stdout); } catch (err) { console.log(); if (err.stderr) { console.error( `${errorAlert} Unable to find "${colors.cyan(template)}" in the npm registry.`, ); } else { console.log(err); } console.error(`${errorAlert} Cannot continue safely. Exiting...`); process.exit(1); } } if (!keywords || !keywords.includes('csa-template')) { console.error( `\n${errorAlert} The template is not a CSA template (missing "${colors.yellow( 'csa-template', )}" keyword in package.json), check the template name to make sure you are using the current template name.`, ); console.error(`${errorAlert} Cannot continue safely. Exiting...`); process.exit(1); } } async function cleanProject(dir) { const packageManifest = path.join(dir, 'package.json'); removeSync(path.join(dir, 'package-lock.json')); removeSync(path.join(dir, 'node_modules')); const {scripts, webDependencies, dependencies, devDependencies} = require(packageManifest); const {prepare, start, build, test, ...otherScripts} = scripts; await fs.promises.writeFile( packageManifest, JSON.stringify( { scripts: {prepare, start, build, test, ...otherScripts}, webDependencies, dependencies, devDependencies, }, null, 2, ), ); const gitignore = path.join(dir, '.gitignore'); if (!fs.existsSync(gitignore)) { await fs.promises.writeFile(gitignore, ['.snowpack', 'build', 'node_modules'].join('\n')); } } async function initializeGitRepo(targetDirectory) { console.log(`\n - Initializing git repo.\n`); try { await execa('git', ['init'], {cwd: targetDirectory}); await execa('git', ['add', '-A'], {cwd: targetDirectory}); await execa('git', ['commit', '-m', 'initial commit'], { cwd: targetDirectory, }); console.log(` - ${colors.green('Success!')}`); } catch (err) { console.log(` - ${colors.yellow('Could not complete git repository initialization.')}`); } } const { template, useYarn, usePnpm, toInstall, toInitializeGitRepo, targetDirectoryRelative, targetDirectory, verbose, } = validateArgs(process.argv); let installer = 'npm'; if (useYarn) { installer = 'yarn'; } else if (usePnpm) { installer = 'pnpm'; } const isLocalTemplate = template.startsWith('.'); // must start with a `.` to be considered local const installedTemplate = isLocalTemplate ? path.resolve(process.cwd(), template) // handle local template : path.join(targetDirectory, 'node_modules', template); // handle template from npm/yarn (async () => { await verifyProjectTemplate(isLocalTemplate, {dir: installedTemplate, template}); console.log(`\n - Using template ${colors.cyan(template)}`); console.log(` - Creating a new project in ${colors.cyan(targetDirectory)}`); fs.mkdirSync(targetDirectory, {recursive: true}); await fs.promises.writeFile(path.join(targetDirectory, 'package.json'), `{"name": "my-csa-app"}`); // fetch from npm or GitHub if not local (which will be most of the time) if (!isLocalTemplate) { try { await execa( 'npm', ['install', template, '--ignore-scripts', '--loglevel', verbose ? 'verbose' : 'error'], { cwd: targetDirectory, all: true, }, ); } catch (err) { // Only log output if the command failed console.error(err.all); throw err; } } await copy(installedTemplate, targetDirectory); await cleanProject(targetDirectory); if (toInstall) { console.log(` - Installing package dependencies. This might take a couple of minutes.\n`); const npmInstallOptions = { cwd: targetDirectory, stdio: 'inherit', }; function installProcess(packageManager) { switch (packageManager) { case 'npm': return execa( 'npm', ['install', '--loglevel', verbose ? 'verbose' : 'error'], npmInstallOptions, ); case 'yarn': return execa('yarn', [verbose ? '--verbose' : '--silent'], npmInstallOptions); case 'pnpm': return execa( 'pnpm', ['install', `--reporter=${verbose ? 'default' : 'silent'}`], npmInstallOptions, ); default: throw new Error('Unspecified package installer.'); } } const npmInstallProcess = installProcess(installer); npmInstallProcess.stdout && npmInstallProcess.stdout.pipe(process.stdout); npmInstallProcess.stderr && npmInstallProcess.stderr.pipe(process.stderr); await npmInstallProcess; } else { console.log(` - Skipping "${installer} install" step\n`); } if (toInitializeGitRepo) { await initializeGitRepo(targetDirectory); } function formatCommand(command, description) { return ' ' + command.padEnd(17) + colors.dim(description); } console.log(``); console.log(colors.bold(colors.underline(`Quickstart:`))); console.log(``); console.log(` cd ${targetDirectoryRelative}`); console.log(` ${installer} start`); console.log(``); console.log(colors.bold(colors.underline(`All Commands:`))); console.log(``); console.log( formatCommand( `${installer} install`, `Install your dependencies. ${ toInstall ? '(We already ran this one for you!)' : '(You asked us to skip this step!)' }`, ), ); console.log(formatCommand(`${installer} start`, 'Start your development server.')); console.log(formatCommand(`${installer} run build`, 'Build your website for production.')); console.log(formatCommand(`${installer} test`, 'Run your tests.')); console.log(``); })(); ================================================ FILE: create-snowpack-app/cli/index.js ================================================ #!/usr/bin/env node 'use strict'; const currentVersion = process.versions.node; const requiredMajorVersion = parseInt(currentVersion.split('.')[0], 10); const minimumMajorVersion = 10; if (requiredMajorVersion < minimumMajorVersion) { console.error(`Node.js v${currentVersion} is out of date and unsupported!`); console.error(`Please use Node.js v${minimumMajorVersion} or higher.`); process.exit(1); } require('./createSnowpackApp'); ================================================ FILE: create-snowpack-app/cli/package.json ================================================ { "name": "create-snowpack-app", "version": "1.10.0", "license": "MIT", "homepage": "https://github.com/withastro/snowpack/tree/main/create-snowpack-app/cli#readme", "repository": { "type": "git", "url": "https://github.com/withastro/snowpack.git", "directory": "create-snowpack-app/cli" }, "publishConfig": { "access": "public" }, "bin": { "create-snowpack-app": "./index.js" }, "dependencies": { "execa": "^5.1.1", "fs-extra": "^9.0.0", "kleur": "^4.1.1", "yargs-parser": "^20.0.0" } } ================================================ FILE: docs/README.md ================================================

Documentation

**📚 To read our documentation, please visit the official [Snowpack website ➞](https://snowpack.dev)** --- **🙋 To contribute to our documentation, please read our [Contributor Guidelines ➞](../CONTRIBUTING.md#Documentation)** ================================================ FILE: docs/concepts/build-pipeline.md ================================================ --- layout: ../../layouts/content.astro title: The Build Pipeline description: Snowpack Build creates a production-ready website with or without a bundler --- ![build output example](/img/snowpack-build-example.png) `snowpack build` - When you're ready to deploy your application, run the build command to generate a static production build of your site. Building is tightly integrated with your dev setup so that you are guaranteed to get a near-exact copy of the same code that you saw during development. ### Bundle for Production **You should be able to use a bundler because you want to, and not because you need to.** That was the original concept that Snowpack was designed to address. Snowpack treats bundling as an optional production optimization, which means you're free to skip over the extra complexity of bundling until you need it. By default, `snowpack build` will build your site using the same unbundled approach as the `dev` command. This is fine for most projects, but you also may still want to bundle for production. Legacy browser support, code minification, code-splitting, tree-shaking, dead code elimination, and other performance optimizations can all be handled in Snowpack via bundling. Bundlers normally require dozens or even hundreds of lines of configuration, but with Snowpack it's just a one-line plugin with no config required. This is possible because Snowpack builds your application _before_ sending it to the bundler, so the bundler never sees your custom source code (JSX, TS, Svelte, Vue, etc.) and instead needs to worry only about building common HTML, CSS, and JS. ```js // Bundlers plugins are pre-configured to work with Snowpack apps. // No config required! You just need to install the plugin first. { "plugins": [["@snowpack/plugin-webpack"]] } ``` See [our bundling guides](/guides/optimize-and-bundle) for more information about connecting bundled (or unbundled) optimization plugins for your production builds. ## Legacy Browser Support You can customize the set of browsers you'd like to support via the `package.json` "browserslist" property, going all the way back to IE11. This will be picked up when you run `snowpack build` to build for production. ```js /* package.json */ "browserslist": ">0.75%, not ie 11, not UCAndroid >0, not OperaMini all", ``` If you're worried about legacy browsers, you should also add a bundler to your production build. Check out our [section on bundling for deployment](/guides/optimize-and-bundle) for more info. Note: During development (`snowpack dev`) we perform no transpilation for older browsers. Make sure that you're using a modern browser during development. ================================================ FILE: docs/concepts/dev-server.md ================================================ --- layout: ../../layouts/content.astro title: The Dev Server description: Snowpack's dev server is fast because it only rebuilds the files you change. Powered by ESM (ES modules). --- ![dev command output example](/img/snowpack-dev-startup-2.png) `snowpack dev` - Snowpack's dev server is an instant dev environment for [unbundled development.](/concepts/how-snowpack-works) The dev server will build a file only when it's requested by the browser. That means that Snowpack can start up instantly (usually in **<50ms**) and scale to infinitely large projects without slowing down. In contrast, it's common to see 30+ second dev startup times when building large apps with a traditional bundler. Snowpack supports JSX & TypeScript source code by default. You can extend your build even further with [custom plugins](/plugins) that connect Snowpack with your favorite build tools: TypeScript, Babel, Vue, Svelte, PostCSS, Sass... go wild! ================================================ FILE: docs/concepts/hot-module-replacement.md ================================================ --- layout: ../../layouts/content.astro title: HMR + Fast Refresh description: Snowpack's ESM-powered unbundled development means near-instant single file builds that only take 10-25ms to load and update in the browser. --- Hot Module Replacement (HMR) is the ability to push file updates to the browser without triggering a full page refresh. Imagine changing some CSS, hitting save, and then instantly seeing your change reflected on the page without a refresh. That's HMR. HMR is not unique to Snowpack. However, Snowpack's ability to leverage ESM for unbundled development introduces near-instant single file builds that only take 10-25ms to load and update in the browser. Snowpack ships with ready, out-of-the-box HMR support for the following file types: - CSS - CSS Modules - JSON JavaScript HMR is also supported out-of-the-box, but often requires a few additional lines of code to properly integrate with your frontend framework's "render" function. See "Enabling HMR + Fast Refresh" below. ## Fast Refresh In addition to normal HMR, Snowpack also supports **Fast Refresh** for most popular frameworks like React, Preact and Svelte. Fast Refresh is a framework-specific enhancement to HMR, which applies single file updates in a way that preserves component state across updates. Changes to a `` component, for example, would be applied without resetting the component's internal state. Fast Refresh makes development even faster, especially when working on popups and other secondary view states that normally would require a click to re-open or re-visit after every change. ## Enabling HMR + Fast Refresh Snowpack supports HMR for all popular frontend frameworks. **[Create Snowpack App (CSA)](https://github.com/withastro/snowpack/blob/main/create-snowpack-app) ships with HMR enabled by default.** You can setup HMR yourself with just a few lines of code, and Fast Refresh can be enabled automatically via plugin: - Preact: [@prefresh/snowpack](https://www.npmjs.com/package/@prefresh/snowpack) - React: [@snowpack/plugin-react-refresh](https://www.npmjs.com/package/@snowpack/plugin-react-refresh) - Svelte: [@snowpack/plugin-svelte](https://www.npmjs.com/package/@snowpack/plugin-svelte) - Vue (HMR only): [A few lines of code](https://github.com/withastro/snowpack/blob/main/create-snowpack-app/app-template-vue/src/index.js#L7-L14) For more advanced HMR integrations, Snowpack created the [esm-hmr spec](https://github.com/snowpackjs/esm-hmr), a standard HMR API for any ESM-based dev environment: ```js // HMR Code Snippet Example if (import.meta.hot) { import.meta.hot.accept(({module}) => { // Accept the module, apply it into your application. }); } ``` Check out the full [ESM-HMR API reference](https://github.com/snowpackjs/esm-hmr) on GitHub. ================================================ FILE: docs/concepts/how-snowpack-works.md ================================================ --- layout: ../../layouts/content.astro title: How Snowpack Works description: Snowpack serves your application unbundled during development. Each file is built only once and is cached until it changes. --- ### Summary **Snowpack is a modern, lightweight build tool for faster web development.** Traditional JavaScript build tools like webpack and Parcel need to rebuild & rebundle entire chunks of your application every time you save a single file. This rebundling step introduces lag between hitting save on your changes and seeing them reflected in the browser. Snowpack serves your application **unbundled during development.** Each file needs to be built only once and then is cached forever. When a file changes, Snowpack rebuilds that single file. There's no time wasted re-bundling every change, just instant updates in the browser (made even faster via [Hot-Module Replacement (HMR)](/concepts/hot-module-replacement)). You can read more about this approach in our [Snowpack 2.0 Release Post.](/posts/2020-05-26-snowpack-2-0-release/) Snowpack's **unbundled development** still supports the same **bundled builds** that you're used to for production. When you go to build your application for production, you can plug in your favorite bundler via an official Snowpack plugin for Webpack or Rollup (coming soon). With Snowpack already handling your build, there's no complex bundler config required. **Snowpack gets you the best of both worlds:** fast, unbundled development with optimized performance in your bundled production builds. ![webpack vs. snowpack diagram](/img/snowpack-unbundled-example-3.png) ### Unbundled Development **Unbundled development** is the idea of shipping individual files to the browser during development. Files can still be built with your favorite tools (like Babel, TypeScript, Sass) and then loaded individually in the browser with dependencies thanks to ESM `import` and `export` syntax. Any time you change a file, Snowpack rebuilds only that file. The alternative is **bundled development.** Almost every popular JavaScript build tool today focuses on bundled development. Running your entire application through a bundler introduces additional work and complexity to your dev workflow that is unnecessary now that ESM is widely supported. Every change -- on every save -- must be rebundled with the rest of your application before your changes can be reflected in your browser. Unbundled development has several advantages over the traditional bundled development approach: - Single-file builds are fast. - Single-file builds are deterministic. - Single-file builds are easier to debug. - Project size doesn’t affect dev speed. - Individual files cache better. That last point is key: **Every file is built individually and cached indefinitely.** Your dev environment will never build a file more than once and your browser will never download a file twice (until it changes). This is the real power of unbundled development. ### Using NPM Dependencies NPM packages are mainly published using a module syntax (Common.js, or CJS) that can't run on the web without some build processing. Even if you write your application using browser-native ESM `import` and `export` statements that would all run directly in the browser, trying to import any one npm package will force you back into bundled development. **Snowpack takes a different approach:** Instead of bundling your entire application for this one requirement, Snowpack processes your dependencies separately. Here's how it works: ``` node_modules/react/**/* -> http://localhost:3000/web_modules/react.js node_modules/react-dom/**/* -> http://localhost:3000/web_modules/react-dom.js ``` 1. Snowpack scans your website/application for all used npm packages. 2. Snowpack reads these installed dependencies from your `node_modules` directory. 3. Snowpack bundles all of your dependencies separately into single JavaScript files. For example: `react` and `react-dom` are converted to `react.js` and `react-dom.js`, respectively. 4. Each resulting file can be run directly in the browser, and imported via ESM `import` statements. 5. Because your dependencies rarely change, Snowpack rarely needs to rebuild them. After Snowpack builds your dependencies, any package can be imported and run directly in the browser with zero additional bundling or tooling required. This ability to import npm packages natively in the browser (without a bundler) is the foundation that all unbundled development and the rest of Snowpack is built on top of. ```html ``` ================================================ FILE: docs/guides/babel.md ================================================ --- layout: ../../layouts/content.astro title: 'Babel' tags: communityGuide published: true img: '/img/logos/babel.svg' imgBackground: '#323330' description: How to use Babel in your Snowpack project. --- [Babel](https://babeljs.io/) is a popular JavaScript transpiler that includes a huge ecosystem of plugins. **You probably don't need Babel!** Snowpack has built-in support for JSX and TypeScript transpilation. Only use Babel if you need to customize how your JavaScript/TypeScript files are built using custom Babel plugins/presets. **To use Babel with Snowpack:** add the [@snowpack/plugin-babel](https://www.npmjs.com/package/@snowpack/plugin-babel) plugin to your project. ```diff // snowpack.config.mjs export default { "plugins": [ + ['@snowpack/plugin-babel'], ], }; ``` ================================================ FILE: docs/guides/connecting-tools.md ================================================ --- layout: ../../layouts/content.astro title: The Snowpack Guide to connecting your favorite tools description: 'How do you use your favorite tools in Snowpack? This Guide will help you get started' published: true --- One of the most common questions we get is "How do I connect my favorite tool to Snowpack?" In this guide we'll go over the three different ways that you can integrate third-party tooling into your Snowpack dev environment or build pipeline: - Snowpack plugin - Integrated CLI script (via `@snowpack/plugin-run-script`) - Run separately, outside of Snowpack (ex: in your `package.json`) ## Integrating a Tool With a Snowpack Plugin The best way to connect a new tool to Snowpack is to search our [plugin catalog](/plugins) for a relevant plugin. Most likely, someone already created a plugin to help you integrate your favorite tool with ease. To add a plugin first install using your package manager, then add the plugin name to the `plugins` section in your Snowpack configuration file. Many plugins have their own totally optional configuration options. These are covered in each plugin's documentation. For example, if you'd like to use sass, you can install [`@snowpack/plugin-sass` ](https://www.npmjs.com/package/@snowpack/plugin-sass) with npm: ```bash npm install @snowpack/plugin-sass ``` Then if you don't already have a Snowpack configuration file (`snowpack.config.mjs`) you can create one with this command: ```bash snowpack init ``` Open up `snowpack.config.mjs` and add the name of your new plugin to the plugins object: ```diff // snowpack.config.mjs export default { plugins: [ - /* ... */ + '@snowpack/plugin-sass', ], }; ``` What about the other optional configuration options? [The `@snowpack/plugin-sass` documentation](https://github.com/withastro/snowpack/tree/main/plugins/plugin-sass) lists all the options and where to put them in the `snowpack.config.mjs` file. If I wanted the `compressed` output `style` I'd turn the `@snowpack/plugin-sass` value into an array with an object containing the configuration: ```diff // snowpack.config.mjs export default { plugins: [ - '@snowpack/plugin-sass' + ['@snowpack/plugin-sass', { style: 'compressed'}] ], }; ``` If there isn't a plugin yet, you might be interested in making one. Check out our [Plugin API](/reference/plugins) ## Connect any other Script/CLI using plugin-run-script and plugin-build-script If you can't find a plugin that fits your needs and don't want to write your own, you can also run CLI commands directly as a part of your build using one of our two utility plugins: `@snowpack/plugin-build-script` & `@snowpack/plugin-run-script`. #### @snowpack/plugin-build-script ```js // snowpack.config.mjs // [npm install @snowpack/plugin-build-script] export default { plugins: [ [ '@snowpack/plugin-build-script', { cmd: 'postcss', input: ['.css'], output: ['.css'], }, ], ], }; ``` This plugin allows you to connect any CLI into your build process. Just give it a `cmd` CLI command that can take input from `stdin` and emit the build result via `stdout`. Check out the [plugin documentation](https://github.com/withastro/snowpack/tree/main/plugins/plugin-build-script) for more information. #### @snowpack/plugin-run-script ```js // snowpack.config.mjs // [npm install @snowpack/plugin-run-script] export default { plugins: [ [ '@snowpack/plugin-run-script', { cmd: 'eleventy', watch: '$1 --watch', }, ], ], }; ``` This plugin allows you to run any CLI command as a part of your dev and build workflow. This plugin doesn't affect your build output, but it is useful for connecting developer tooling directly into Snowpack. Use this to add meaningful feedback to your dev console as you type, like TypeScript type-checking and ESLint lint errors. This doesn't affect how Snowpack builds your site. Check out the [plugin documentation](https://github.com/withastro/snowpack/tree/main/plugins/plugin-run-script) for more information. ### Examples #### PostCSS ```js // snowpack.config.mjs export default { plugins: [ [ '@snowpack/plugin-build-script', { cmd: 'postcss', input: ['.css'], output: ['.css'], }, ], ], }; ``` The [`postcss-cli`](https://github.com/postcss/postcss-cli) package must be installed manually. You can configure PostCSS with a `postcss.config.js` file in your current working directory. #### ESLint ```js // snowpack.config.mjs export default { plugins: [ [ '@snowpack/plugin-run-script', { cmd: 'eslint src --ext .js,.jsx,.ts,.tsx', // Optional: Use npm package "eslint-watch" to run on every file change watch: 'esw -w --clear src --ext .js,.jsx,.ts,.tsx', }, ], ], }; ``` ================================================ FILE: docs/guides/hmr.md ================================================ --- layout: ../../layouts/content.astro title: Hot Module Replacement (HMR) description: Enable Snowpack's Hot Module Replacement (HMR) on your development server. published: false ---
This article is a stub, you can help fix it by removing duplicate content from /concepts/hot-module-replacement and writing it in a how-to guide format
Hot Module Replacement (HMR) is the ability for Snowpack to push file changes to the browser without triggering a full page refresh. This article is about enabling HMR and connecting to the HMR dev server. For instructions on using HMR, refer to the `import.meta.hot` API documentation. ### Is HMR Connected? You can tell if HMR is connected by checking your browser dev console. If you see the following message, that means that HMR is enabled and connected. ``` [ESM-HMR] listening for file changes.. ``` ### Enable HMR: Snowpack Dev Server HMR is enabled by default when you run `snowpack dev`. The Snowpack dev server will add the necessary scripts for your browser, and no configuration is required for most users. You can toggle this support off during development via the [`devOptions.hmr` configuration option](/reference/configuration). ### Enable HMR: Custom Server _Note: Full HMR is not yet supported. Only full page reloads are currently working. Follow updates here: [https://github.com/withastro/snowpack/issues/1935](https://github.com/withastro/snowpack/issues/1935)_ If you use your own server (ex: Rails) to serve your application during development, there are a couple of small steps to enable HMR. HMR is not enabled by default if you are using `snowpack build --watch` for local development (instead of `snowpack dev`). Set `devOptions.hmr: true` in your Snowpack configuration (or, use `--hmr`) to enable HMR support in your application. We also recommend that you manually add the Snowpack HMR client to your HTML (development only, not needed in production): ```html ``` ### Configuring HMR HMR is powered by a WebSocket connection between the client and Snowpack. If you are having trouble connecting to the client, you may need need to tell it where Snowpack's HMR Websocket server is running. First, make sure that `devOptions.hmr` is set to true. This guarantees that the HMR Websocket is running and ready to accept connections in both `dev` and `build`. By default, the client will try to connect to the HMR server at the current host (ex: `localhost`) using one of two ports: - `snowpack dev`: The same port as the dev server - `snowpack build`: port `12321` You can control this by setting `devOptions.hmrPort` manually via configuration or setting the following global script variable somewhere on the page **before** the `hmr-client.js` script runs: ```html ``` ### Disable HMR Set `devOptions.hmr` to false to disable HMR in all cases. ================================================ FILE: docs/guides/https-ssl-certificates.md ================================================ --- layout: ../../layouts/content.astro title: SSL Certificates description: How to use HTTPs during development and generate SSL certifcates for your Snowpack build. ---
This guide has an example repo: examples/https-ssl-certificates
``` npm start -- --secure ``` Snowpack provides an easy way to use a local HTTPS server during development through the use of the `--secure` flag. When enabled, Snowpack will look for a `snowpack.key` and `snowpack.crt` file in the root directory and use that to create an HTTPS server with HTTP2 support enabled. Optionally, you may customize which TLS certificate and private key files by passing them directly to `devOptions.secure`. ```js const fs = require('fs'); const cert = fs.readFileSync('/path/to/server.crt'); const key = fs.readFileSync('/path/to/server.key'); module.exports = { devOptions: { secure: {cert, key}, }, }; ``` ### Generating SSL Certificates You can automatically generate credentials for your project via either: - [devcert (no install required, but openssl is a prerequisite)](https://github.com/davewasmer/devcert-cli): `npx devcert-cli generate localhost` - [mkcert (install required)](https://github.com/FiloSottile/mkcert): `mkcert -install && mkcert -key-file snowpack.key -cert-file snowpack.crt localhost` In most situations you should add personally generated certificate files (`snowpack.key` and `snowpack.crt`) to your `.gitignore` file. ================================================ FILE: docs/guides/jest.md ================================================ --- layout: ../../layouts/content.astro title: 'Jest' tags: communityGuide img: '/img/logos/jest.svg' imgBackground: '#d14c53' published: true description: How to use Jest, a popular test runner, with Snowpack. --- [Jest](https://jestjs.io/) is a popular Node.js test runner for Node.js & web projects. Jest can be used with any frontend project as long as you configure how Jest should build your frontend files to run on Node.js. Many projects will try to manage this configuration for you, since it can get complicated. Snowpack ships pre-built Jest configuration files for several popular frameworks. If you need to use Jest for any reason,consider extending one of these packages: - React: [@snowpack/app-scripts-react](https://www.npmjs.com/package/@snowpack/app-scripts-react) - Preact: [@snowpack/app-scripts-preact](https://www.npmjs.com/package/@snowpack/app-scripts-preact) - Svelte: [@snowpack/app-scripts-svelte](https://www.npmjs.com/package/@snowpack/app-scripts-svelte) Note: You will need a `jest.setup.js` file in the root directory of your project. ### Example ```js // jest.config.js // Example: extending a pre-built Jest configuration file module.exports = { ...require('@snowpack/app-scripts-preact/jest.config.js')(), }; ``` ================================================ FILE: docs/guides/optimize-and-bundle.md ================================================ --- layout: ../../layouts/content.astro title: Optimize & Bundle for Production published: true description: How to optimize your Snowpack build for production, with or without a bundler. --- `snowpack build` builds your site into web native JS, CSS, and HTML files. This "unbundled" deployment can be enough for small sites, but many developers prefer to optimize and bundle their final site for production performance. Snowpack can run all sorts of optimizations on your final build to handle legacy browser support, code minification, code-splitting, tree-shaking, dead code elimination, preloading, bundling, and more. Snowpack build optimizations come in two flavors: **built-in** (esbuild) & **plugin** (webpack, rollup, or whatever else you might like to run). ### Option 1: Built-in Optimizations Snowpack recently released a built-in optimization pipeline powered by [esbuild](https://esbuild.github.io/). Using this built-in optimizer, you can now bundle, transpile, and minify your production builds 10x-100x faster than Webpack or Rollup. However, esbuild is still young and [not yet production-ready](https://esbuild.github.io/faq/#production-readiness). At the moment, we only recommended this for smaller projects. ```js // snowpack.config.mjs // Example: Using Snowpack's built-in bundling support export default { optimize: { bundle: true, minify: true, target: 'es2018', }, }; ``` The full supported interface is: ```ts export interface OptimizeOptions { entrypoints: 'auto' | string[] | ((options: {files: string[]}) => string[]); preload: boolean; bundle: boolean; loader?: {[ext: string]: Loader}; sourcemap: boolean | 'external' | 'inline' | 'both'; splitting: boolean; treeshake: boolean; manifest: boolean; minify: boolean; target: 'es2020' | 'es2019' | 'es2018' | 'es2017'; } ``` ### Option 2: Optimize Plugins Snowpack supports popular bundlers via plugin: - webpack (recommended!): [@snowpack/plugin-webpack](https://www.npmjs.com/package/@snowpack/plugin-webpack) - Rollup: [snowpack-plugin-rollup-bundle](https://github.com/ParamagicDev/snowpack-plugin-rollup-bundle) **For now, we recommend using @snowpack/plugin-webpack until our built-in optimize support is more mature.** Check out our [Plugins Catalog](/plugins) to browse all available Snowpack plugins, and read the [Plugins Guide](/guides/plugins) if you're interested in creating your own. ================================================ FILE: docs/guides/plugins.md ================================================ --- layout: ../../layouts/content.astro title: Creating Your Own Plugin description: Learn the basics of our Plugin API through working examples. --- A **Snowpack plugin** lets you extend Snowpack with new behaviors. Plugins can hook into different stages of the Snowpack build pipeline to add support for new file types and your favorite dev tools. Add plugins to support Svelte, compile Sass to CSS, convert SVGs to React components, bundle your final build, type check during development, and much more. This guide takes you though creating and publishing your first plugin. - The basic structure of Snowpack plugins - How to choose the right hooks from the Snowpack Plugin API - How to publish your plugin and add it to our [Plugin](/plugins) directory Prerequisites: Snowpack plugins are written in JavaScript and run via Node.js so basic knowledge of both is required. ## Creating and testing your first plugin In this step you'll create a simple plugin scaffold that you can turn into a fuctional plugin based on the examples in the guide. Create a directory for your plugin called `my-snowpack-plugin` and inside it create a `my-snowpack-plugin.js` file: ```js // my-snowpack-plugin.js // Example: a basic Snowpack plugin file, customize the name of the file and the value of the name in the object // snowpackConfig = The Snowpack configuration object // pluginOptions = user-provided configuration options module.exports = function (snowpackConfig, pluginOptions) { return { name: 'my-snowpack-plugin', }; }; ``` To test your new plugin, run `npm init` to create a basic `package.json` then run `npm link` in your plugin’s directory to expose the plugin globally (on your development machine). For testing, [create a new, example Snowpack project](/tutorials/getting-started) in a different directory. In your example Snowpack project, run `npm install && npm link my-snowpack-plugin` (use the name from your plugin’s `package.json`). > The alternative would be to use `npm install --save-dev path_to_your_plugin`, which would create the "symlink-like" entry in your example Snowpack project’s `package.json` In your example Snowpack project, add your plugin to the `snowpack.config.mjs` along with any plugin options you’d like to test: ```js // snowpack.config.mjs // Example: enabling a Snowpack plugin called "my-snowpack-plugin" export default { plugins: ['my-snowpack-plugin'], }; ``` ## Testing and Troubleshooting - TODO: create a full how to test procedure - HINT: Add `--verbose` to the command to see the steps, e.g. `snowpack dev --verbose` or `snowpack build --verbose` ## Adding user-configurable options to your plugin TODO make this a real example In this step, you'll learn how to add user-configurable options to your plugin and to use them in your plugin code. In your example Snowpack project, instead of enabling the plugin as a string containing the plugin name, use an array. The first item is name of your plugin and the second a new object containing the plugin options. ```diff // snowpack.config.mjs export default { plugins: [ - 'my-snowpack-plugin' + ['my-snowpack-plugin', { optionA: 'foo', optionB: true }] ] }; ``` You access these through the `pluginOptions` ```diff // my-snowpack-plugin.js module.exports = function (snowpackConfig, pluginOptions) { + let optionA = pluginOptions.optionA; + let optionB = pluginOptions.optionB; return { name: 'my-snowpack-plugin', }; }; ``` ### Plugin Use-Cases Snowpack uses an internal **Build Pipeline** to build files in your application for development and production. Every source file passes through the build pipeline, which means that Snowpack can build more than just JavaScript. Images, CSS, SVGs and more can all be built by Snowpack. #### Build Plugins Snowpack finds the first plugin that claims to `resolve` the given file. It then calls that plugin's `load()` method to load the file into your application. This is where compiled languages (TypeScript, Sass, JSX, etc.) are loaded and compiled to something that can run on the web (JS, CSS, etc). #### Transform Plugins Once loaded, every file passes through the build pipeline again to run through matching `transform()` methods of all plugins that offer the method. Plugins can transform a file to modify its contents before finishing the file build. #### Dev Tooling Plugins Snowpack plugins support a `run()` method which lets you run any CLI tool and connect its output into Snowpack. You can use this to run your favorite dev tools (linters, TypeScript, etc.) with Snowpack and automatically report their output back through the Snowpack developer console. If the command fails, you can optionally fail your production build. #### Bundler Plugins Snowpack builds you a runnable, unbundled website by default, but you can optimize this final build with your favorite bundler (webpack, Rollup, Parcel, etc.) through the plugin `optimize()` method. When a bundler plugin is used, Snowpack will run the bundler on your build automatically to optimize it. See our official [@snowpack/plugin-webpack](https://github.com/withastro/snowpack/tree/main/plugins/plugin-webpack) bundler plugin for an example of using the current interface. ### Example: Getting Started To create a Snowpack plugin, you can start with the following file template: ```js // my-snowpack-plugin.js module.exports = function (snowpackConfig, pluginOptions) { return { name: 'my-snowpack-plugin', // ... }; }; ``` ```js // snowpack.config.mjs export default { plugins: [ [ './my-snowpack-plugin.js', { optionA: 'foo', optionB: 'bar', }, ], ], }; ``` A Snowpack plugin should be distributed as a function that can be called with plugin-specific options to return a plugin object. Snowpack will automatically call this function to load your plugin. That function accepts 2 parameters, in this order: 1. the [Snowpack configuration object](/reference/configuration) (`snowpackConfig`) 1. (optional) user-provided config options (`pluginOptions`) ### Example: Transform a File For our first example, we’ll look at transforming a file. ```js module.exports = function (snowpackConfig, pluginOptions) { return { name: 'my-commenter-plugin', async transform({id, contents, isDev, fileExt}) { if (fileExt === '.js') { return `/* I’m a comment! */ ${contents}`; } }, }; }; ``` The object returned by this function is a **Snowpack Plugin**. A plugin consists of a `name` property and some hooks into the Snowpack lifecycle to customizes your build pipeline or dev environment. In the example above we have: - The **name** property: The name of your plugin. This is usually the same as your package name if published to npm. - The **transform** method: A function that allows you to transform & modify built files. In this case, we add a simple comment (`/* I’m a comment */`) to the beginning of every JS file in your build. This covers the basics of single-file transformations. In our next example, we’ll show how to compile a source file and change the file extension in the process. ### Example: Build From Source When you build files from source, you also have the ability to transform the file type from source code to web code. In this example, we'll use Babel to load several types of files as input and output JavaScript in the final build: ```js const babel = require('@babel/core'); module.exports = function (snowpackConfig, pluginOptions) { return { name: 'my-babel-plugin', resolve: { input: ['.js', '.jsx', '.ts', '.tsx', '.mjs'], output: ['.js'], }, async load({filePath}) { const result = await babel.transformFileAsync(filePath); return result.code; }, }; }; ``` This is a simplified version of the official Snowpack Babel plugin, which builds all JavaScript, TypeScript, and JSX files in your application with the `load()` method. The `load()` method is responsible for loading and build files from disk while the `resolve` property tells Snowpack which files the plugin can load and what to expect as output. In this case, the plugin claims responsibility for files matching any of the file extensions found in `resolve.input`, and outputs `.js` JavaScript (declared via `resolve.output`). `resolve.output` can also use a multi-part extension such as `.module.css` or `.hbs.js`—files will be matched from most specific extension to least. **See it in action:** Let's say that we have a source file at `src/components/App.jsx`. Because the `.jsx` file extension matches an extension in our plugin's `resolve.input` array, Snowpack lets this plugin claim responsibility for loading this file. `load()` executes, Babel builds the JSX input file from disk, and JavaScript is returned to the final build. ### Example: Multi-File Building For a more complicated example, we’ll take one input file (`.svelte`) and use it to generate 2 output files (`.js` and `.css`). ```js const fs = require('fs').promises; const svelte = require('svelte/compiler'); module.exports = function (snowpackConfig, pluginOptions) { return { name: 'my-svelte-plugin', resolve: { input: ['.svelte'], output: ['.js', '.css'], }, async load({filePath}) { const fileContents = await fs.readFile(filePath, 'utf-8'); const {js, css} = svelte.compile(fileContents, {filename: filePath}); return { '.js': js && js.code, '.css': css && css.code, }; }, }; }; ``` This is a simplified version of the official Snowpack Svelte plugin. Don't worry if you're not familiar with Svelte, just know that building a Svelte file (`.svelte`) generates both JS & CSS for our final build. In that case, the `resolve` property takes only a single `input` file type (`['.svelte']`) but two `output` file types (`['.js', '.css']`). This matches the result of Svelte's build process and the returned entries of our `load()` method. **See it in action:** Let's say that we have a source file at `src/components/App.svelte`. Because the `.svelte` file extension matches an extension in our plugin's `resolve.input` array, Snowpack lets this plugin claim responsibility for loading this file. `load()` executes, Svelte builds the file from disk, and both JavaScript & CSS are returned to the final build. Notice that `.svelte` is missing from `resolve.output` and isn't returned by `load()`. Only the files returned by the `load()` method are included in the final build. If you wanted your plugin to keep the original source file in your final build, you could add `{ '.svelte': contents }` to the return object. ### Example: Server-Side Rendering (SSR) Plugins can produce server-optimized code for SSR via the `load()` plugin hook. The `isSSR` flag tells the plugin that Snowpack is requesting your file for the server, and that it will expect a response that will run on the server. Some frameworks/languages (like React) run the same code on both the browser and the server. Others (like Svelte) will create different output for the server than the browser. In the example below, we use the `isSSR` flag to tell the Svelte compiler to generate server-optimized code when requested by Snowpack. ```js const svelte = require('svelte/compiler'); const fs = require('fs'); module.exports = function (snowpackConfig, pluginOptions) { return { name: 'basic-svelte-plugin', resolve: { input: ['.svelte'], output: ['.js', '.css'], }, async load({filePath, isSSR}) { const svelteOptions = { /* ... */ }; const codeToCompile = fs.readFileSync(filePath, 'utf-8'); const result = svelte.compile(codeToCompile, { ...svelteOptions, ssr: isSSR, }); // ... }, }; }; ``` If you're not sure if your plugin needs special SSR support, you are probably fine to skip this and ignore the `isSSR` flag in your plugin. Many languages won't need this, and SSR is always an intentional opt-in by the user. ### Example: Optimizing & Bundling Snowpack supports pluggable bundlers and other build optimizations via the `optimize()` hook. This method runs after the build and gives plugins a chance to optimize the final build directory. Webpack, Rollup, and other build-only optimizations should use this hook. ```js module.exports = function(snowpackConfig, pluginOptions) { return { name: 'my-custom-webpack-plugin', async optimize({ buildDirectory }) { await webpack.run({...}); } }; }; ``` This is an (obviously) simplified version of the `@snowpack/plugin-webpack` plugin. When the build command has finished building your application, this plugin hook is called with the `buildDirectory` path as an argument. It's up to the plugin to read build files from this directory and write any changes back to the directory. Changes should be made in place, so write files only at the end and be sure to clean up after yourself (if a file is no longer needed after optimizing/bundling, it is safe to remove). ### Testing To develop and test a Snowpack plugin, the strategy is the same as with other npm packages: - Create your new plugin project (either with `npm init` or `yarn init`) with, for example, npm name: `my-snowpack-plugin` and paste in it the above-mentioned code snipped - Run `npm link` in your plugin’s project folder to expose the plugin globally (in regard to your development machine). - Create a new, example Snowpack project in a different location for testing - In your example Snowpack project, run `npm install && npm link my-snowpack-plugin` (use the name from your plugin’s `package.json`). - Be aware that `npm install` will remove your linked plugin, so on any install, you will need to redo the `npm link my-snowpack-plugin`. - (The alternative would be to use `npm install --save-dev <folder_to_your_plugin_project>`, which would create the "symlink-like" entry in your example Snowpack project’s `package.json`) In your example Snowpack project, add your plugin to the `snowpack.config.mjs` along with any plugin options you’d like to test: ```js export default { plugins: [ [ 'my-snowpack-plugin', { 'option-1': 'testing', 'another-option': false, }, ], ], }; ``` ### Publishing a Plugin To share a plugin with the world, you can publish it to npm. For example, take a look at [snowpack-plugin-starter-template](https://github.com/withastro/snowpack-plugin-starter-template) which can get you up-and-running quickly. You can either copy this outright or simply take what you need. In general, make sure to mind the following checklist: - ✔️ Your `package.json` file has a `main` entry pointing to the final build - ✔️ Your code is compiled to run on Node >= 10 - ✔️ Your package README contains a list of custom options, if your plugin is configurable ### Tips / Gotchas - Remember: A source file will always be loaded by the first `load()` plugin to claim it, but the build result will be run through every `transform` function. - Snowpack will always keep the original file name (`App`) and only ever change the extension in the build. - Extensions in Snowpack always have a leading `.` character (e.g. `.js`, `.ts`). This is to match Node’s `path.extname()` behavior, as well as make sure we’re not matching extension substrings (e.g. if we matched `js` at the end of a file, we also don’t want to match `.mjs` files by accident; we want to be explicit there). - The `resolve.input` and `resolve.output` file extension arrays are vital to how Snowpack understands your build pipeline, and are always required for `load()` to run correctly. - If `load()` doesn't return anything, the file isn’t loaded and the `load()` of the next suitable plugin is called. - If `transform()` doesn't return anything, the file isn’t transformed. - If you want to build a plugin that runs some code only on initialization (such as `@snowpack/plugin-dotenv`), put your side-effect code inside the function that returns your plugin. But be sure to still return a plugin object. A simple `{ name }` object will do. ================================================ FILE: docs/guides/postcss.md ================================================ --- layout: ../../layouts/content.astro title: 'PostCSS' tags: communityGuide published: true img: '/img/logos/postcss.svg' imgBackground: '#f8f8f2' description: How to use PostCSS in your Snowpack project. --- [PostCSS](https://postcss.org/) is a popular CSS transpiler with support for [a huge ecosystem of plugins.](https://github.com/postcss/postcss#plugins) **To use PostCSS with Snowpack:** Install [@snowpack/plugin-postcss](https://www.npmjs.com/package/@snowpack/plugin-postcss), [PostCSS](https://www.npmjs.com/package/postcss), and your PostCSS plugins, then add this plugin to your Snowpack config. ```diff // snowpack.config.mjs export default { + plugins: ['@snowpack/plugin-postcss'], }; ``` Lastly, add a `postcss.config.js` file. By default, @snowpack/plugin-postcss looks for this in the root directory of your project, but you can customize this with the `config` option. See [the plugin README](https://www.npmjs.com/package/@snowpack/plugin-postcss) for all available options. ```js // postcss.config.js module.exports = { plugins: [ // Replace below with your plugins require('cssnano'), require('postcss-preset-env'), ], }; ``` Be aware that this plugin will run on all CSS in your project, including any files that compiled to CSS (like `.scss` Sass files). ================================================ FILE: docs/guides/preact.md ================================================ --- layout: ../../layouts/content.astro title: Preact tags: communityGuide img: '/img/logos/preact.svg' imgBackground: '#333333' description: With Snowpack you can import and use Preact without any custom configuration needed. --- You can import and use Preact without any custom configuration needed. **To use `preact/compat`:** (the Preact+React compatability layer) alias the "compat" package to React in your install options: ```js // Example: Lets you import "react" in your application, but uses preact internally // snowpack.config.mjs export default { alias: { react: 'preact/compat', 'react-dom': 'preact/compat', }, }; ``` ================================================ FILE: docs/guides/react-global-imports.md ================================================ --- layout: ../../layouts/content.astro title: React + babel-plugin-import-global published: false ---
This guide has an example repo: examples/react-global-imports
_Based on [app-template-react][app-template-react]_ Example of using Snowpack in conjuction with [babel-plugin-import-global][babel-plugin-import-global]. This is useful when you need to need to inject an import statement at the top of every file, such as React: ```jsx // "import React from 'react'" no longer needed! function MyComponent() { // … } export default MyComponent; ``` To recreate this setup, follow 2 steps: 1. Create a [babel.config.js](./babel.config.js) file in the root of the project. Copy the settings shown. 2. Install [@snowpack/plugin-babel][snowpack-babel] and add it to [snowpack.config.mjs](./snowpack.config.js) ### ⚠️ Caveat When you use [@snowpack/plugin-babel][snowpack-babel], you miss out on the faster builds that come from Snowpack‘s default JS builder, [esbuild][esbuild] (we don‘t run both together to avoid conflict). However, if you skip Babel, you will have to manually place `import` statements yourself at the top of every file. We‘d recommend being explicit and manually managing every `import` statement yourself. You can simplify your setup, speed up your builds, and you might see benefits from being explicit. In order to do this, simply use our [React starter template][app-template-react]. No setup required. But if you‘ve weighed the tradeoffs and decide that a slower build is worth it to get global import functionality, then start from the example here. [app-template-react]: https://github.com/withastro/snowpack/create-snowpack-app/app-template-react [babel-plugin-import-global]: https://www.npmjs.com/package/babel-plugin-import-global [esbuild]: https://esbuild.github.io/ [snowpack-babel]: https://github.com/withastro/snowpack/plugins/plugin-babel ================================================ FILE: docs/guides/react-loadable-components.md ================================================ --- layout: ../../layouts/content.astro title: React + Loadable Components published: false ---
This guide has an example repo: examples/react-loadable-components
_Based on [app-template-react][app-template-react]_ You can lazy load React components in Snowpack when needed with React‘s builtin `React.lazy` ([docs][react-lazy]): ```jsx import React, {useState, useEffect, Suspense} from 'react'; const Async = React.lazy(() => import('./Async')); function Component() { return (
Loading...
}> ); } ``` This works out-of-the-box in Snowpack, with no configuration needed! ### Learn more - [`React.lazy` documentation on reactjs.org][react-lazy] [react-lazy]: https://reactjs.org/docs/code-splitting.html#reactlazy ================================================ FILE: docs/guides/routing.md ================================================ --- layout: ../../layouts/content.astro title: Routing published: true description: This guide will walk you through some common routing scenarios and how to configure the routes option to support them in development. --- As a web build tool, Snowpack has no knowledge of how (or where) your application is served in production. But Snowpack's dev server can be customized to recreate something close to your production environment for local development. This guide will walk you through some common routing scenarios and how to configure the `routes` option to support them in development. ### Scenario 1: SPA Fallback Paths Single Page Applications (SPA) give the client application complete control over routing logic. The web host itself has no idea what is a valid route and what is a 404, since that logic lives completely in the client. Therefore, every route (valid or not) must be served the same HTML response that will load and run the HTML/JS/CSS application in the browser. This special file is called the "SPA Fallback". To implement this pattern, you'll want to define a single "catch-all" route for development: ```js // snowpack.config.mjs export default { routes: [ { match: 'routes', src: '.*', dest: '/index.html', }, ], }; ``` This tells Snowpack's dev server to serve the fallback `/index.html` URL for all routes (`.*` in RegEx means "match everything"). `"match": "routes"` refers to all URLs that either do not include a file extension or that include the ".html" file extension. If you changed the above example to `"match": "all"` instead, then all URLs (including JS, CSS, Image files and more) would respond with the fallback HTML file. ### Scenario 2: Proxy API Paths Many modern frontend applications will talk directly to an API. Often this API is hosted as a seperate application at another domain (ex: `api.example.com/users`) and no special server configuration is needed to talk with it. However in some cases, your API may be hosted at the same domain as your website using a different path scheme (ex: `www.example.com/api/users`). To serve the correct API response to a URL like `/api/users` in development, you can configure Snowpack to proxy some requests to another server. In this example, we'll proxy all "/api/\*" requests to another server that we have running locally on port `3001`: ```js // snowpack.config.mjs import proxy from 'http2-proxy'; export default { routes: [ { src: '/api/.*', dest: (req, res) => { // remove /api prefix (optional) req.url = req.url.replace(/^\/api\//, '/'); return proxy.web(req, res, { hostname: 'localhost', port: 3001, }); }, }, ], }; ``` We recommend the [http2-proxy](https://www.npmjs.com/package/http2-proxy) library for proxying requests to another server, which supports a wide range of options to customize how each request is proxied. But feel free to implement the `dest` proxy function however you like. Your own server logic could even be called directly inside of the `dest` function, however this is not recommended over proxying. ### Scenario 3: Proxy WebSocket Requests Proxied requests can be upgraded to a WebSocket connection via the "upgrade" event handler. This allows you to proxy WebSocket requests through the Snowpack dev server during development. You can learn more about the upgrade mechanism on [MDN Web Docs.](https://developer.mozilla.org/en-US/docs/Web/HTTP/Protocol_upgrade_mechanism#upgrading_to_a_websocket_connection). ```js // snowpack.config.mjs import proxy = from 'http2-proxy'; export default { routes: [ { src: '/ws', upgrade: (req, socket, head) => { const defaultWSHandler = (err, req, socket, head) => { if (err) { console.error('proxy error', err); socket.destroy(); } }; proxy.ws( req, socket, head, { hostname: 'localhost', port: 3001, }, defaultWSHandler, ); }, }, ], }; ``` ### Scenario 4: Custom Server Rendering If you only use Snowpack to build assets and rely on your own custom server (ex: Rails, Laravel, etc) for serving HTML, then you probably have no use for routing. Consider reading our guide on [Server-Side Rendering (SSR)](/guides/server-side-render) which explains how to integrate Snowpack into your own server as middleware. ================================================ FILE: docs/guides/sass.md ================================================ --- layout: ../../layouts/content.astro title: 'Sass' tags: communityGuide published: true img: '/img/logos/sass.svg' imgBackground: '#bf4080' description: How to use SASS with Snowpack using the Snowpack SASS plugin --- [Sass](https://www.sass-lang.com/) is a stylesheet language that’s compiled to CSS. It allows you to use variables, nested rules, mixins, functions, and more, all with a fully CSS-compatible syntax. Sass helps keep large stylesheets well-organized and makes it easy to share design within and across projects. To use Sass with Snowpack, install [@snowpack/plugin-sass](https://www.npmjs.com/package/@snowpack/plugin-sass) (Sass automatically included) and add it to your `snowpack.config.mjs` file. See README on npm or [GitHub](https://github.com/withastro/snowpack/tree/main/plugins/plugin-sass#plugin-options) for plugin options. ```diff // snowpack.config.mjs export default { plugins: [ + '@snowpack/plugin-sass', ], }; ``` ================================================ FILE: docs/guides/server-side-render.md ================================================ --- layout: ../../layouts/content.astro title: Server-Side Rendering (SSR) description: This guide will walk you through three different options for setting up Snowpack with your own custom server. published: true --- Server-side rendering (SSR) can refer to several similar developer stories: - Using Snowpack with a server web framework like Rails or Express - Using Snowpack to power a server-side frontend framework kit like Next.js or SvelteKit - Any project configuration where your HTML is generated at runtime, outside of your static build. This guide will walk you through three options for setting up Snowpack with your own custom server: 1. `snowpack build --watch` - Serve files out of the static build directory. 2. `startServer({ ... })` - Serve files on-demand via Snowpack's JavaScript API. 3. `getServerRuntime({ ... })` - Run your built JS files server-side, directly inside of Node.js. ### Option 1: Static Serving Serving built files directly out of Snowpack's `build/` directory is the easiest way to get started with Snowpack. Run `snowpack build` to build your site to a static directory, and then make sure that your HTML server response includes the appropriate `script` & `link` tags to load your Snowpack-built JavaScript and CSS: ```html ``` During development, Snowpack will rebuild files on every change thanks to the `--watch` command. To enable dev features like automatic page reloads and hot module replacement (HMR), check out the ["Custom Server" section](/guides/hmr#enable-hmr%3A-custom-server) of our HMR guide for more info. This setup also has the benefit of pulling from the same `build/` directory in both development and production. You can control this `build/` output behavior yourself by passing different `--out` CLI flags to Snowpack for development vs production. You can even pass entirely different config files via the `--config` CLI flag, or put custom logic in your `snowpack.config.mjs` file to behave differently for different builds. The downside of this static approach is that you need to wait for Snowpack to build the entire `build/` directory on startup before your site will run. This is something that all other build tools (like Webpack) have to deal with, but Snowpack has the ability to build files only when they are requested by the browser, leading to ~0ms startup wait time. ### Option 2: On Demand Serving (Middleware) The best developer experience is achieved by loading files on-demand. This removes any need for work on startup, giving you a faster developer environment no matter how large your project grows. ```js const {startServer} = require('snowpack'); const server = await startServer({ ... }); // Example: Express // On request, build each file on request and respond with its built contents app.use((req, res, next) => { try { const buildResult = await server.loadUrl(req.url); res.send(buildResult.contents); } catch (err) { next(err); } }); ``` Note that you'll still need to set up static file serving out of the `build/` directory for production deployments. For that reason, this can be seen as an enhancement over the static setup in Option 1 for faster development speeds. While our official API is written in JavaScript and requires Node.js to run, you could implement your own API for any language/environment using the `snowpack dev` CLI command to start the server and loading assets directly by fetching each URL. ### Option 3: Server-Side Rendering (SSR) Some frontend applications are also designed to run on the server. In the two previous sections, we've just been loading and serving Snowpack files to the client. In this final section, we'll look into how your project can run Snowpack-built JavaScript on the server and return server-rendered HTML to the client for a faster first page load. Snowpack provides an Node.js SSR Runtime API to help you run & render your application server-side. `getServerRuntime()` returns a `runtime` instance that can be used to import Snowpack-built modules into your current Node.js process, on-demand. This runtime handles the transformation from browser ESM to Node.js Common.js (CJS) so that it can run directly in server without issues. ```js const {readFileSync} = require('fs'); const {startServer} = require('snowpack'); const server = await startServer({ ... }); const runtime = server.getServerRuntime(); // Advanced Example: Express + React SSR app.use(async (req, res, next) => { // Server-side import our React component const importedComponent = await runtime.importModule('/dist/MyReactComponent.js'); const MyReactComponent = importedComponent.exports.default; // Render your react component to HTML const html = ReactDOMServer.renderToString(React.createElement(MyReactComponent, null)); // Load contents of index.html const htmlFile = readFileSync('./index.html', 'utf8'); // Inserts the rendered React HTML into our main div const document = htmlFile.replace(/
<\/div>/, `
${html}
`); // Sends the response back to the client res.send(document); }); ``` `getServerRuntime()` is a lower-level tool to help you implement SSR in your project. However, building a custom SSR setup is an advanced task. If you prefer not to implement this yourself, check out some of the new Snowpack-powered application frameworks and static site generators like [SvelteKit](https://svelte.dev/blog/whats-the-deal-with-sveltekit) and [Microsite](https://www.npmjs.com/package/microsite). ================================================ FILE: docs/guides/streaming-imports.md ================================================ --- layout: ../../layouts/content.astro title: Streaming Imports published: true stream: Fetch your npm dependencies on-demand from a remote ESM CDN. --- Snowpack v3.0 introduces a new feature called **Streaming Imports** that fetches imported packages on-demand during development and building. By managing your frontend dependencies with Snowpack, you can leave `npm` for your tooling-only packages or even drop your dependency on `npm`/`yarn`/`pnpm` all together. ## Enable Streaming Imports ```js // snowpack.config.mjs export default { packageOptions: { source: 'remote', }, }; ``` Set `packageOptions.source` to "remote" to enable streaming imports. This tells Snowpack to fetch your imports from the Skypack CDN instead of bundling them locally. Read our [full documentation on `packageOptions`](/reference/configuration#packageoptions.source%3Dremote) to learn more about customizing this behavior. ## How Streaming Imports Work When you enable streaming imports and run `snowpack dev`, the local server will start fetching all imports from `https://pkg.snowpack.dev`. For example, `import "preact"` in your project will become something like `import "https://pkg.snowpack.dev/preact"` in the browser. This tells Snowpack (or the browser) to import your package by URL, and only fetch the package ESM when needed. Snowpack is able to cache the response for future, offline use. The translation is done by the web server, so your source file will still contain the bare `import "preact"` statement. When you run `snowpack build`, the generated files in the build folder will contain `import '../_snowpack/pkg/preact.js';`. `pkg.snowpack.dev` is our ESM Package CDN, powered by [Skypack](https://www.skypack.dev/). Every npm package is hosted as ESM, and any legacy non-ESM packages are upconverted to ESM on the CDN itself. > _**Note:** In some rare cases, a nested import from a package will result in an invalid resource reference on Skypack. Explicitly setting the `packageOptions.origin` to `"https://cdn.skypack.dev"` (rather than using the default `pkg.snowpack.dev` shim) seems to resolve the issue._ ## Benefits of Streaming Imports Streaming dependencies have several benefits over the traditional "npm install + local bundling" approach: - **Speed:** Skip the install + build steps for dependencies, and load your dependencies as pre-build ESM code directly from an ESM CDN like [Skypack](https://www.skypack.dev/). Dependencies are cached locally for offline reuse. - **Safety:** ESM packages are pre-built and never given access to [run code on your machine](https://www.usenix.org/system/files/sec19-zimmermann.pdf). Packages only run in the browser sandbox. - **Simplicity:** ESM packages are managed by Snowpack, so frontend projects that don't need Node.js (Rails, PHP, etc.) can drop the `npm` CLI entirely if they choose. - **No Impact on Final Build:** Streaming imports are still transpiled and bundled with the rest of your final build, and tree-shaken to your exact imports. The end result is a final build that's nearly identical to what it would have been otherwise. ## Snowpack-Managed Dependencies By default, Snowpack fetches the latest version of every package available. Breaking changes are possible over time without a way to manage your dependencies by version. Snowpack uses a `snowpack.deps.json` in your project to manage your dependency versions. If you're familiar with `npm install`, your `snowpack.deps.json` file is like a combined `package.json` and `package-lock.json`. Two commands are available to work with this file: `snowpack add` and `snowpack rm`. Running `snowpack add [package-name]` for the first time will create a new `snowpack.deps.json` file in your project to store information about your new dependency, like desired SemVer version range and lockfile information. ## Using Streaming Imports with TypeScript ```js // snowpack.config.mjs /w TypeScript Support export default { packageOptions: { source: 'remote', types: true, }, }; ``` Setting `types=true` tells Snowpack to install TypeScript types in your project. Snowpack will install those types into a local `.snowpack/types` directory in your project, which you can then point to in your project `tsconfig.json` to get automatic types for your npm packages: ```js // Example: tsconfig.json /w Snowpack streaming imports "baseUrl": "./", "paths": {"*": [".snowpack/types/*"]}, ``` When you start your project (with either `snowpack dev` or `snowpack build`) Snowpack will sync this `.snowpack/types` directory and download any new types that you might need. You can also trigger a sync anytime manually via `snowpack prepare`. ## Using Streaming Imports with Non-JS Packages (Svelte, Vue, etc.) Skypack (the CDN that powers `pkg.snowpack.dev`) will always prefer a package JavaScript entrypoint over any source `.svelte` and `.vue` files. This works for most packages (including most Svelte & Vue packages) but may cause trouble in some projects. In a future release, we'll add better support to build these kinds of packages locally. ## What do I do if a package isn't supported / working? Skypack (the CDN that powers `pkg.snowpack.dev`) is always improving, and its goal is to support all packages. If you find a package that doesn't work, report it to [Skypack's issue tracker](https://github.com/snowpackjs/skypack-cdn/issues) on GitHub. Many of Snowpack's core contributors also work on Skypack, and will be happy to take a look at the broken package. In a future release, we'll add better support to replace broken packages locally. ================================================ FILE: docs/guides/tailwind-css.md ================================================ --- layout: ../../layouts/content.astro title: 'Tailwind CSS' tags: communityGuide published: true img: '/img/logos/tailwind.svg' imgBackground: '#f2f8f8' description: How to use Tailwind CSS with Snowpack. --- [Tailwind](https://tailwindcss.com) is a popular class-based CSS utility library. Loading it in Snowpack is easy, and only requires a few steps! ## Setup Tailwind’s [JIT mode][tailwind-jit] is the new, recommended way to use Tailwind. To set this up in a Snowpack project, do the following: #### 1. Install dependencies From the root of your project, install tailwindcss, PostCSS, and the Snowpack PostCSS plugin. ``` npm install --save-dev tailwindcss @snowpack/plugin-postcss postcss ``` #### 2. Configure You’ll need to create two files in the root of your project: `postcss.config.js` and `tailwind.config.js`: ```js // postcss.config.js module.exports = { plugins: { tailwindcss: {}, // other plugins can go here, such as autoprefixer }, }; ``` ```js // tailwind.config.js module.exports = { mode: 'jit', purge: ['./public/**/*.html', './src/**/*.{js,jsx,ts,tsx,vue}'], // specify other options here }; ``` _Note: be sure to set `purge: []` correctly for your project structure_ Also, you’ll need to add the Snowpack PostCSS plugin to your Snowpack config, and set the [Tailwind config option][config-tailwind], if you haven‘t already: ```diff // snowpack.config.mjs export default { mount: { src: '/_dist', public: '/', }, + devOptions: { + tailwindConfig: './tailwind.config.js', + }, + plugins: [ + '@snowpack/plugin-postcss', + ], }; ``` #### 3. Import Tailwind in your CSS From any global CSS file, add the [Tailwind utilites][tailwind-utilities] you need (if you don’t have a global CSS file, we recommend creating one at `/public/global.css`): ```css @tailwind base; @tailwind components; @tailwind utilities; ``` When you load these with Snowpack, you should see these replaced with Tailwind CSS instead. ⚠️ Make sure you’re importing this file in your main HTML file, like so: ```diff + ``` ## More reading - [Official Tailwind Documentation][tailwind-postcss] - [PostCSS + Snowpack][snowpack-postcss] [config-tailwind]: https://snowpack.dev/reference/configuration#devoptions.tailwindConfig [snowpack-postcss]: /guides/postcss/ [tailwind-jit]: https://tailwindcss.com/docs/just-in-time-mode [tailwind-postcss]: https://tailwindcss.com/docs/installation/#using-tailwind-with-postcss [tailwind-utilities]: https://tailwindcss.com/docs/adding-new-utilities#using-css ================================================ FILE: docs/guides/testing.md ================================================ --- layout: ../../layouts/content.astro title: Testing published: true description: How to choose and use a JavaScript test runner for your Snowpack site. --- Snowpack supports all of the popular JavaScript testing frameworks that you're already familiar with. Mocha, Jest, Jasmine, AVA and Cypress are all supported in Snowpack applications, if integrated correctly. **We currently recommend [@web/test-runner](https://www.npmjs.com/package/@web/test-runner) (WTR) for testing in Snowpack projects.** When benchmarked, it performed faster than Jest (our previous recommendation) while also providing an environment for testing that more closely matches production. Most importantly, WTR runs the same Snowpack build pipeline that you've already configured for your project, so there's no second build configuration needed to run your tests. This improves test confidence while removing 100s of extra build dependencies to your project. ### Testing Guides - [@web/test-runner](/guides/web-test-runner) (Recommended) - [jest](/guides/jest) ================================================ FILE: docs/guides/upgrade-guide.md ================================================ --- layout: ../../layouts/content.astro title: Snowpack Upgrade Guide published: true description: How to upgrade to Snowpack v3 from older versions of Snowpack. --- Snowpack v3.0 was released on January 12th with several new features and some breaking changes. This guide is designed to help you upgrade a project from older versions of Snowpack v1 or v2. ## Upgrading from Snowpack v3 Release Candidate Our v3.0 Release Candidate was meant to be a close-to-final release, but some major changes still got in between then and the v3.0 final release. Snowpack will warn when any outdated APIs are used, and guide you through the upgrade process. In the future, Snowpack Release Candidates will be much closer to final API. ## Upgrading from Snowpack v2 Snowpack v3.0 was mainly designed around new features, and therefore didn't have many major breaking changes introduced. However, there are some changes to be aware of: - **Config name changes:** There was some cleanup of legacy behavior and renaming of old configuration values. Snowpack will warn you of all known name changes when run Snowpack v3.0 for the first time, with instructions to help you upgrade. - **package.json "homepage" no longer sets "baseUrl":** This was a behavior of Create React App that we'd originally tried to match. However, it became confusing to explain to users. In Snowpack v3.0, set "buildOptions.baseUrl" directly. - **Improved support for relative paths in configuration:** All relative paths in configuration files are now relative to the configuration file itself. Previously, all relative config paths were resolved based on the current working directory of wherever you ran Snowpack, which meant that behavior of the config file changed depending on where you ran it. You can also now set the project `"root"`/`--root` directory, which is useful if you run Snowpack from a different directory than the project iself (ex: in monorepos). - **More clear build file naming:** Snowpack v3.0 introduced some cleanup around build file names that you may see when you upgrade projects. The biggest change is to files like `.svelte` and `.vue`, which now have their JS & CSS built to `.svelte.js` and `.svelte.css` respectively. This change shouldn't be noticable to most, but it is good to know. - **More clear handling of absolute import URLs:** In Snowpack, it's now possible to import something by its final URL using an absolute URL. `import "/dist/index.js"`, for example, will now import whatever file is built to `/dist/index.js`. Previously, this behavior was undefined. Relative URLs can still be used to import files relative to the source file itself. ## Upgrading from Snowpack v1 Snowpack v1 only supported installing npm packages as ESM, and had a more limited scope than Snowpack does today. If you are coming from Snowpack v1.0, you may be able to use our internal package installer library [esinstall](https://www.npmjs.com/package/esinstall) directly. `esinstall` is a JavaScript library that implements most of what Snowpack v1.0 gave you via the command-line. `snowpack build` builds your site into web native JS, CSS, and HTML files. This "unbundled" deployment can be enough for small sites, but many developers prefer to optimize and bundle their final site for production performance. Snowpack can run all sorts of optimizations on your final build to handle legacy browser support, code minification, code-splitting, tree-shaking, dead code elimination, preloading, bundling, and more. Snowpack build optimizations come in two flavors: **built-in** (esbuild) & **plugin** (webpack, rollup, or whatever else you might like to run). ### Option 1: Built-in Optimizations Snowpack recently released a built-in optimization pipeline powered by [esbuild](https://esbuild.github.io/). Using this built-in optimizer, you can now bundle, transpile, and minify your production builds 10x-100x faster than Webpack or Rollup. However, esbuild is still young and [not yet production-ready](https://esbuild.github.io/faq/#production-readiness). At the moment, we only recommended this for smaller projects. ```js // snowpack.config.mjs // Example: Using Snowpack's built-in bundling support export default { optimize: { bundle: true, minify: true, target: 'es2018', }, }; ``` The full supported interface is: ```ts export interface OptimizeOptions { entrypoints: 'auto' | string[] | ((options: {files: string[]}) => string[]); preload: boolean; bundle: boolean; loader?: {[ext: string]: Loader}; sourcemap: boolean | 'external' | 'inline' | 'both'; splitting: boolean; treeshake: boolean; manifest: boolean; minify: boolean; target: 'es2020' | 'es2019' | 'es2018' | 'es2017'; } ``` ### Option 2: Optimize Plugins Snowpack supports popular bundlers via plugin: - webpack (recommended!): [@snowpack/plugin-webpack](https://www.npmjs.com/package/@snowpack/plugin-webpack) - Rollup: [snowpack-plugin-rollup-bundle](https://github.com/ParamagicDev/snowpack-plugin-rollup-bundle) **For now, we recommend using @snowpack/plugin-webpack until our built-in optimize support is more mature.** Check out our [Plugins Catalog](/plugins) to browse all available Snowpack plugins, and read the [Plugins Guide](/guides/plugins) if you're interested in creating your own. ================================================ FILE: docs/guides/wasm.md ================================================ --- layout: ../../layouts/content.astro title: 'WASM' tags: communityGuide published: true img: '/img/logos/wasm.svg' imgBackground: '#f2f2f8' description: How to use WASM in your Snowpack project. --- [WASM (short for WebAssembly)](https://webassembly.org/) is a portable compilation target for programming languages, enabling deployment on the web for client and server applications. **To use WASM with Snowpack:** Use the browser's native [`WebAssembly`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly) & [`fetch`](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) APIs to load a WASM file into your application: ```js // Example: Load WASM in your project const wasm = await WebAssembly.instantiateStreaming( fetch('/example.wasm'), /* { ... } */ ); ``` In the future, we may add `import "/example.wasm"` ESM import support to automate this support for you. However, today WASM import support differs from bundler-to-bundler. In any case, WASM import support would just be a shortcut or wrapper around the code snippet above. You can recreate this helper today in your own project: ```js // Example: WASM Loader (move this into some utility/helper file for reuse) export function loadWasm(url, importObject = {module: {}, env: {abort() {}}}) => { const result = await WebAssembly.instantiateStreaming(fetch(url), importObject); return result.instance; // or, return result; } const wasm = await loadWasm('/example.wasm'); ``` ================================================ FILE: docs/guides/web-test-runner.md ================================================ --- layout: ../../layouts/content.astro title: '@web/test-runner' tags: communityGuide img: '/img/logos/modern-web.svg' imgBackground: '#f2f2f8' description: How to use @web/test-runner in your Snowpack project. --- [@web/test-runner](https://www.npmjs.com/package/@web/test-runner) is our recommended test runner for Snowpack projects. Read more about why we recommend @web/test-runner in our [Snowpack Testing Guide](/guides/testing). ## Setup This guide shows how to set up @web/test-runner and [@snowpack/web-test-runner-plugin](https://www.npmjs.com/package/@snowpack/web-test-runner-plugin) for a React project. The end result recreates the test configuration in [app-template-react](https://github.com/withastro/snowpack/blob/main/create-snowpack-app/app-template-react), one of our Create Snowpack App starter templates. If you're using a different framework, tweak React specific steps appropriately. #### 1. Install dependencies The base testing dependencies (don't hit Enter just yet!): ``` npm install --save-dev @web/test-runner @snowpack/web-test-runner-plugin chai ``` If using React, Vue, Svelte, or Preact, add the corresponding [Testing Library](https://testing-library.com/) (in this case `@testing-library/react`). If using TypeScript, add `@types/mocha` and `@types/chai`. #### 2. Configure Create a new `web-test-runner.config.js` file in your project root: ```js process.env.NODE_ENV = 'test'; module.exports = { plugins: [require('@snowpack/web-test-runner-plugin')()], }; ``` ⚠️ Don't add @snowpack/web-test-runner-plugin to plugins in your `snowpack.config.mjs` file! It only needs to be in `web-test-runner.config.js`. If you need to specify test options, use [testOptions](https://www.snowpack.dev/reference/configuration#testoptions). #### 3. Script Add a `test` script to your project `package.json`: ```diff "scripts": { "start": "snowpack dev", "build": "snowpack build", + "test": "web-test-runner \\\"src/**/*.test.jsx\\\"", ... }, ``` If needed, swap `.jsx` with the file type(s) containing your tests. To specify multiple test file types, enclose with curly brackets and separate with commas. For example, to match `.jsx`, `.js`, and `.ts` files, the script would be: ``` "test": "web-test-runner \\\"src/**/*.test.{jsx,js,ts}\\\"", ``` > 💡 Tip: `wtr` can be used as a shorthand for `web-test-runner`. ================================================ FILE: docs/guides/web-worker.md ================================================ --- layout: ../../layouts/content.astro title: 'Web Workers' tags: communityGuide published: true description: How to use Web Workers in your Snowpack project. --- [Web Workers](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers) are a simple means for web content to run scripts in background threads. **To use Web Workers with Snowpack:** Use the browser's native [Web Worker API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers#Web_Workers_API) directly: ```js // Example: Load a Web Worker in your project const myWorker = new Worker(new URL('./worker.js', import.meta.url)); ``` Passing a `URL` to the Worker constructor (instead of a string literal) is recommended, but not required. Using a string literal (ex: `new Worker('./worker.js')`) may prevent some optimizations when you build your site for production. Also note that the URL passed to the `Worker` must match the final URL which may differ from the path on disk. For example, `./worker.js` would still be used even if the original file on disk was `worker.ts`. `mount` destinations should also be used here, if needed. ### ESM Web Worker Support **ESM syntax (`import`/`export`) in Web Workers is still not supported in all modern browsers.** Snowpack v3.0.0 and the Snowpack Webpack v5 plugin will both support automatic bundling once released. Until then, you'll need to write self-contained, single-file Web Workers (no ESM `import`/`export` statements) or pre-bundle your web workers yourself. ```js const worker = new Worker(new URL('./esm-worker.js', import.meta.url), { name: 'my-worker', type: 'module', }); ``` ================================================ FILE: docs/guides/workbox.md ================================================ --- layout: ../../layouts/content.astro title: Workbox tags: communityGuide description: The Workbox CLI integrates well with Snowpack. ---
This article is a stub, you can help expand it into how-to guide format
The [Workbox CLI](https://developers.google.com/web/tools/workbox/modules/workbox-cli) integrates well with Snowpack. Run the wizard to bootstrap your first configuration file, and then run `workbox generateSW` to generate your service worker. Remember that Workbox expects to be run every time you deploy, as a part of a production build process. If you don't have one yet, create package.json [`"deploy"` and/or `"build"` scripts](https://michael-kuehnel.de/tooling/2018/03/22/helpers-and-tips-for-npm-run-scripts.html) to automate your production build process. ================================================ FILE: docs/posts/2020-05-26-snowpack-2-0-release.md ================================================ --- layout: ../../layouts/post.astro bannerVideo: '/img/extra-space-4.mp4' permalink: '/posts/2020-05-26-snowpack-2-0-release/' title: Snowpack v2.0 description: 'Build web applications with less tooling and faster iteration.' tagline: v2.0.0 release post date: 2020-05-26 --- After 40+ beta versions & release candidates we are very excited to introduce **Snowpack 2.0: A build system for the modern web.** - Starts up in <50ms and stays fast in large projects. - Bundle-free development with bundled production builds. - Built-in support for TypeScript, JSX, CSS Modules and more. - Works with React, Preact, Vue, Svelte, and all your favorite libraries. - [Create Snowpack App (CSA)](https://github.com/withastro/snowpack/tree/main/create-snowpack-app/cli) starter templates.
```bash # install with npm npm install --save-dev snowpack # install with yarn yarn add --dev snowpack ``` ## The Road to Snowpack 2.0 Snowpack 1.0 was designed for a simple mission: install npm packages to run directly in the browser. The theory was that JavaScript packages are the only thing still **_requiring_** the use of a bundler during development. Remove that requirement, remove the bundler, and speed up web development for everyone. Guess what? It worked! Thousands of developers started using Snowpack to install their dependencies and build websites with less tooling. A whole new type of faster, lighter-weight dev environment suddenly became possible. **Snowpack 2.0 is a build system designed for this new era of web development.** Snowpack removes the bundler from your dev environment, leveraging native [ES Module (ESM)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import) support to serve built files directly to the browser. This isn't just a faster tool, it's a new approach to web build systems. ## The Rise of O(1) Build Systems ![webpack vs. snowpack diagram](/img/snowpack-unbundled-example-3.png) **Bundling is a process of `O(n)` complexity.** When you change a file, you can't just rebuild that one file. You often need to rebuild and rebundle an entire chunk of your application across multiple related files to properly accept the new changes. **Snowpack is an O(1) build system.** The term was first coined by [Ives van Hoorne](https://www.youtube.com/watch?v=Yu9zcJJ4Uz0) and it perfectly encapsulates our vision for the future of web development. Every file built with Snowpack can be expressed as a function: `build(file) => result`. When a file is changed during development, only that file is rebuilt. This has several advantages over the traditional bundled dev approach: - O(1) builds are faster. - O(1) builds are predictable. - O(1) builds are easy to reason about & configure. - Project size doesn't affect build time during development. - Individual files cache better. That last point is key: every built file is cached individually and reused indefinitely. **If you never change a file, you will never need to re-build it again.** ## `dev` A Faster Dev Environment ![dev command output example](/img/snowpack-dev-startup-2.png) Run `snowpack dev` to start your new web dev environment and the first thing you'll notice is **how flippin' fast O(1) build tooling is.** Snowpack starts up in less than 50ms. That's no typo: 50 milliseconds or less. With no bundling work needed to start, your server spins up immediately. On your very first page load, Snowpack builds your first requested files and then caches them for future use. Even if your project contains a million different files, Snowpack builds only those needed to load the current page. This is how Snowpack stays fast. `snowpack dev` includes a development server and a bunch of familiar features right out of the box: - TypeScript Support - JSX Support - Hot Module Replacement (HMR) - Importing CSS & CSS Modules - Importing Images & Other Assets - Custom Routing - Proxying Requests ## Customizing Your Build [Build Scripts](/concepts/build-pipeline) let you connect your favorite build tools. With Snowpack, you express every build as a linear `input -> build -> output` workflow. This allow Snowpack to pipe your files into and out of any existing UNIX-y CLI tools without the need for a special plugin ecosystem. ```js // snowpack.config.json { "scripts": { // Pipe every "*.css" file through the PostCSS CLI // stdin (source file) > postcss > stdout (build output) "build:css": "postcss", } } ``` If you've ever used your `package.json` "scripts" config, this format should feel familiar. We love the simplicity of using your CLIs directly without an unnecessary plugin system. We hope this pattern offers a similar intuitive design. If you want more control over your build (or want to write your own build tool) Snowpack also supports [third-party JavaScript plugins](/plugins). [Check out our docs](/concepts/build-pipeline) to learn more about customizing your build. ## `build` Bundling for Production ![build output example](/img/snowpack-build-example.png) To be clear, Snowpack isn't against bundling for production. In fact, we recommend it. File minification, compression, dead-code elimination and network optimizations can all make a bundled site run faster for your users, which is the ultimate goal of any build tool. Snowpack treats bundling as a final, production-only build optimization. By bundling as the final step, you avoid mixing build logic and bundle logic in the same huge configuration file. Instead, your bundler gets already-built files and can focus solely on what it does best: bundling. Snowpack maintains official plugins for both Webpack & Parcel. Connect your favorite, and then run `snowpack build` to build your site for production. ```js // snowpack.config.json { // Optimize your production builds with Webpack "plugins": [["@snowpack/plugin-webpack", {/* ... */}]] } ``` If you don't want to use a bundler, that's okay too. Snowpack's default build will give you an unbundled site that also runs just fine. This is what the Snowpack project has been all about from the start: **Use a bundler because you want to, and not because you need to.** ## Try Snowpack Today We are so excited to share this all with you today. Download Snowpack to experience the future of web development. ``` npm i snowpack@latest --save-dev ``` If you already have an existing Snowpack application, Snowpack 2.0 will walk you through updating any outdated configuration. Snowpack's original package installer still works as expected, and with the new `dev` & `build` commands Snowpack even manages your web packages for you. **[Check out our docs site to learn more.](https://www.snowpack.dev/)** #### Create Snowpack App The easiest way to get started with Snowpack is with [Create Snowpack App (CSA)](https://github.com/withastro/snowpack). CSA automatically initializes a starter application for you with a pre-configured, Snowpack-powered dev environment. ```bash npx create-snowpack-app new-dir --template [SELECT FROM BELOW] [--use-yarn] ``` - [@snowpack/app-template-blank](https://github.com/withastro/snowpack/tree/main/create-snowpack-app/app-template-blank) - [@snowpack/app-template-react](https://github.com/withastro/snowpack/tree/main/create-snowpack-app/app-template-react) - [@snowpack/app-template-react-typescript](https://github.com/withastro/snowpack/tree/main/create-snowpack-app/app-template-react-typescript) - [@snowpack/app-template-preact](https://github.com/withastro/snowpack/tree/main/create-snowpack-app/app-template-preact) - [@snowpack/app-template-svelte](https://github.com/withastro/snowpack/tree/main/create-snowpack-app/app-template-svelte) - [@snowpack/app-template-vue](https://github.com/withastro/snowpack/tree/main/create-snowpack-app/app-template-vue) - [@snowpack/app-template-lit-element](https://github.com/withastro/snowpack/tree/main/create-snowpack-app/app-template-lit-element) - [@snowpack/app-template-11ty](https://github.com/withastro/snowpack/tree/main/create-snowpack-app/app-template-11ty) - **[See all community templates](https://github.com/withastro/snowpack/tree/main/create-snowpack-app)** 🐹 Happy hacking! --- _Thank you to all of our [80+ contributors](https://github.com/withastro/snowpack/graphs/contributors) for making this release possible._ _Thanks to [Melissa McEwen](https://twitter.com/melissamcewen) & [@TheoOnTwitch](https://twitter.com/TheoOnTwitch) for helping to edit this post._ ================================================ FILE: docs/posts/2020-07-30-snowpack-2-7-release.md ================================================ --- layout: ../../layouts/post.astro title: Snowpack 2.7 description: 'A new plugin API plus smaller, faster production builds.' tagline: v2.7.0 release post permalink: '/posts/2020-07-30-snowpack-2-7-release/' date: 2020-07-30 bannerImage: '/img/banner-2.jpg' --- Happy release day! We are excited to announce Snowpack v2.7 with a handful of new features improving stability and the overall developer experience: - **Redesigned plugin API** plus [new guides for plugin authors](/plugins) - **Import aliasing** and new ways to customize Snowpack - **Improved build performance** with smaller, faster builds - **New Svelte + TypeScript** app templates - **Bug fixes, usability improvements & more!**
Plus, we hit some VERY exciting project milestones last month: - ❤️ **150** [open source contributors](https://github.com/withastro/snowpack/graphs/contributors) (and growing!) - 🏆 **1000+** discussions, issues, and pull requests - ⭐️ **10,000+** stars on GitHub - 👋 **New companies using Snowpack:** [Alibaba](https://www.1688.com/) & [Airhacks](https://airhacks.com/)
If you've been waiting for an excuse to give Snowpack a try, now is a great time to start! Try out a Create Snowpack App (CSA) template or install Snowpack into any existing project: ```bash # install with npm npm install --save-dev snowpack # install with yarn yarn add --dev snowpack ``` ## Redesigned Plugin API Snowpack v2.7 features an major rewrite of our internal build pipeline to support a more reliable and expressive plugin API. New optimizations and file type builders are unlocked with the redesigned `load()`, `transform()`, `run()` and `optimize()` plugin hooks (with more on the way in future versions). ![snowpack screenshot](/img/snowpack-27-screenshot-1.png) Snowpack 2.7 is fully backwards compatible with older plugins, so you can upgrade Snowpack without worrying about version mismatches. Every hook is documented in our new [Plugins Guide](/plugins) for plugin authors. The new API is heavily inspired by [Rollup](https://rollupjs.org/), so we hope it already feels familiar to many of you. ## Simplified Configuration ![snowpack screenshot](/img/snowpack-27-screenshot-3.png) Snowpack v2.0 originally introduced the concept of build `"scripts"` as a way to configure anything from file building to HTTP request proxying. Scripts were flexible, but hard to document and frustrating to debug. Our internal plugin rewrite presented an opportunity to improve the developer experience while keeping the flexibility of direct CLI tooling. You can now connect third-party tooling directly into Snowpack's build pipeline using one of two new utility plugins: - `@snowpack/plugin-build-script`: Use any CLI directly to build files for Snowpack. - `@snowpack/plugin-run-script`: Run arbitrary CLI commands during dev/build. Other options like `mount`, `proxy`, and `alias` (see below) are now easier to customize as well with top-level config options that take the guesswork out of common configuration. The `"scripts"` configuration format will continue to be supported in Snowpack v2, but we recommend migrating any custom scripts to `"plugins"` and plan to remove support in a future major release. ## New: Import Aliasing ![snowpack screenshot](/img/snowpack-27-screenshot-2.png) In previous versions of Snowpack, import aliasing was hard to understand and configure (and it didn’t support all types of aliasing). Starting in Snowpack v2.7, [Import Aliases](/reference/configuration) gets a new top-level `alias` config so that you can define as many custom aliases as you'd like. Package import aliases are also supported. ## Improved Build Performance Snowpack's official webpack plugin is now more powerful than ever, with new support for multi-page website bundling and better default performance settings (based on the [latest research from Google](https://web.dev/granular-chunking-nextjs/)). Special shout out to [@mxmul](https://github.com/mxmul) (Yelp) for leading these community contributions! If you don't use a bundler in production, you'll still see a smaller build. That's because Snowpack v2.7 now ships with minification on by default. We plan to keep improving the default unbundled build performance story over the next few releases, so stay tuned. ## Svelte + TypeScript Support ![snowpack screenshot](/img/svelte-ts.png) Last week, [Svelte announced official support for TypeScript](https://svelte.dev/blog/svelte-and-typescript). We're huge fans of both projects and couldn't pass up the chance to test the new support out in a brand new Svelte + TypeScript app template for Snowpack. Visit [Create Snowpack App](https://github.com/withastro/snowpack/tree/main/create-snowpack-app) for a list of all of our new app templates. ## Thank You, Contributors! Finally, Snowpack wouldn't be possible without the [150+ contributors](https://github.com/withastro/snowpack/graphs/contributors) who contributed features, fixes, and documentation improvements. Thanks again for all of your help. -- Snowpack Maintainers
Psst... In case you missed it, check out our latest project: Skypack - the new JavaScript CDN that lets you load any npm package directly in the browser.
Follow @snowpackjs on Twitter and don't miss future updates! ================================================ FILE: docs/posts/2020-12-03-snowpack-3-release-candidate.md ================================================ --- layout: ../../layouts/post.astro title: 'Snowpack v3.0 Release Candidate' tagline: New features to change the way you build for the web. description: 'New features to change the way you build for the web. Snowpack v3.0 will release on January 6th, 2021 (the one-year anniversary of its original launch post). This is our biggest release yet with some serious new features, including a new way to load npm packages on-demand that lets you skip the `npm install` step entirely.' date: 2020-12-03 --- **tl;dr:** Snowpack v3.0 will release on January 6th, 2021 (the one-year anniversary of its original launch post). This is our biggest release yet with some serious new features, including **a new way to load npm imports on-demand** and skip the frontend `npm install` step entirely. **Update:** Release was delayed for a week for some finishing touches. New release date is January 13th! [More info on Discord](https://discord.com/channels/712696926406967308/783454799051489301/796785330932940800). Best of all: it's all available to try today! ## What's New? Snowpack v3 will focus on the polish & official release of four features already available today in the current version of Snowpack (v2.18.0) under the `experiments` flag: - `experiments.source` - Streaming npm imports, no install step required. - `experiments.optimize` - Built-in bundling, preloading, and asset minifying. - `experiments.routes` - Advanced config for HTML fallbacks and API proxies. - `import 'snowpack'` - A brand new JavaScript API for Snowpack integrations. ## New: Streaming Package Imports Snowpack has always pushed the limits of frontend development, and this release is no different. Snowpack v3.0 introduces an exciting new feature to speed up & simplify your development workflow. Typically, JavaScript dependencies are installed and managed locally by a package manager CLI like `npm`, `yarn` or `pnpm`. Packages are often bloated with unrelated files and almost never run directly in the browser. Additional steps are required to process, build and bundle these installed packages so that they can actually run in browser. **What if we could simplify this? What if Snowpack could skip the "npm install" step entirely and just fetch the relevant, pre-built package code on-demand via ESM?** ```js // you do this: import * as React from 'react'; // but get behavior like this: import * as React from 'https://cdn.skypack.dev/react@17.0.1'; ``` That URL in the example above points to [Skypack](https://www.skypack.dev/), a popular JavaScript CDN that we built to serve every npm package as ESM. Importing dependencies by URL like this is well supported in Snowpack, Deno, and all major browsers. But writing these URLs directly into your source code isn't ideal and makes development impossible without a network connection. **Snowpack v3.0 brings together the best of both worlds:** Get the simplicity of `import 'react'` in your own source code and let Snowpack fetch these dependencies behind the scenes, pre-built and ready to run in the browser. Snowpack caches everything for you automatically, so you can continue to work offline without relying on Skypack besides the first package fetch. This has several benefits over the traditional "npm install" approach: - **Speed:** Skip the install + build steps for dependencies, and load your dependencies as pre-build ESM code. - **Safety:** ESM packages are pre-built into JavaScript for you and never given access to [run code on your machine](https://www.usenix.org/system/files/sec19-zimmermann.pdf). Third-party code only ever run in the browser sandbox. - **Simplicity:** ESM packages are managed by Snowpack, so frontend projects that don't need Node.js (Rails, PHP, etc.) can drop the npm CLI entirely if they choose. - **Same Final Build:** When you build your site for production, package code is transpiled with the rest of your site and tree-shaken to your exact imports, resulting in a final build that's nearly identical. If this all sounds too wild for you, don't worry. This is **100% opt-in** behavior for those who want it. By default, Snowpack will continue to pull your npm package dependencies out of your project `node_modules` directory like it always has. Check out our guide on [Streaming Package Imports](/guides/streaming-imports) to learn more about how to enable this new behavior in your project today. In a future release, we hope to open this up to custom ESM package sources and other CDNs as well. ![js api](/img/post-snowpackv3-esbuild.png) ## Built-in Optimizations, Powered by esbuild [esbuild](https://esbuild.github.io/) is a marvel: it performs 100× faster than most other popular bundlers and over 300× faster than Parcel (by esbuild's own benchmarks). esbuild is written in Go, a compiled language that can parallelize heavy bundling workloads where other popular bundlers -- written in JavaScript -- cannot. Snowpack already uses esbuild internally as our default single-file builder for JavaScript, TypeScript and JSX files. Snowpack v3.0 takes this integration one step further, with a new built-in build optimization pipeline. Bundle, minify, and transpile your site for production in 1/100th of the time of other bundlers. Snowpack is able to adopt esbuild today thanks to an early bet that we made on the future of bundling: **bundling is a post-build optimization, and not the core foundation that everything is built on top of.** Thanks to this early design decision, esbuild can be plugged in and swapped out of your Snowpack build as easily as any other bundler. esbuild is still a young project, but it's future looks promising. In the meantime, we will also continue to invest in the existing Webpack & Rollup bundler plugins for a long time to come. To get started, check out the `experiments.optimize` option in our newest [Optimizing Your Snowpack Build](/guides/optimize-and-bundle) guide. ![js api](/img/post-snowpackv3-routes.png) ## Routing Snowpack's new `experiments.routes` configuration lets you define routes that align your dev environment with production. This unlocks some interesting new use-cases, including: - **API Proxies** - Route all `/api/*` URLs to another URL or localhost port. - **SPA Fallbacks** - Serve an app shell `index.html` to all requested routes. - **Faster Site Loads** - Speed up your site and serve different HTML shell files for each route. - **Island Architecture** - Serve HTML that renders individual components on the page, in parallel. (Made popular by [Jason Miller](https://twitter.com/_developit) in [this blog post](https://jasonformat.com/islands-architecture/)). - **Mimic Vercel/Netlify** - Re-create your Vercel or Netlify routes in development. Or, create a Snowpack plugin to automatically generate these routes from your `vercel.json` or `_redirects` file at startup. While API proxying and SPA fallbacks have already been supported in Snowpack for a while now, this brings them all together into a single, expressive new API. ![js api](/img/post-snowpackv3-jsapi.png) ## A New JavaScript API Snowpack's new JavaScript API grants you more advanced control over Snowpack's dev server and build pipeline, helping you build more powerful integrations on top of Snowpack to unlock new kinds of dev tooling and server-side rendering (SSR) solutions. The Svelte team recently made news with [SvelteKit](https://svelte.dev/blog/whats-the-deal-with-sveltekit): An official, zero-effort SSR app framework for Svelte apps. SvelteKit is powered internally by Snowpack, using our brand-new JavaScript API to manage your build pipeline and build files on-demand. Snowpack speeds up development and helps to cut SvelteKit's startup time to near-zero. Check out our new [JavaScript API reference](/reference/javascript-interface) to start building your own custom integrations on top of Snowpack. Or, read through our new guide on [Server-Side Rendering](/guides/server-side-render) to get started with a custom SSR integration for production. ## Installation You can install the Snowpack v3.0 release candidate today by running: ``` npm install snowpack@next ``` Since all v3.0 features already exist in Snowpack today, our existing documentation site applies to both v2 & v3. At this point only very old, undocumented, legacy behavior has been removed from the `next` v3.0 branch. Features under the `experiments` flag may continue to change as we get closer to the official release date. By the end of the year, you can expect that these features will move out from behind the `experiments` flag and into top-level config objects in the `next` v3.0 branch. Learn more at [snowpack.dev](https://www.snowpack.dev). Happy hacking! ================================================ FILE: docs/posts/2021-01-13-snowpack-3-0.md ================================================ --- layout: ../../layouts/post.astro title: 'Snowpack v3.0' description: Snowpack v3.0 is here! Our biggest release yet with some serious new features, including pre-bundled streaming imports, built-in bundling & optimizations, new JavaScript APIs, and more.' date: 2021-01-13 --- Snowpack v3.0 is here! This is our biggest release yet with brand new features including: - **Pre-bundled streaming imports** - Import any npm package, on-demand. - **Integrated build optimizations** - Built-in bundling, preloading, minification, and more. - **JavaScript API** - Integrate with Snowpack's brand new native JS API. - **Node.js Runtime API** - Import your Snowpack-built files directly into Node.js. - **Bug fixes, stability improvements, and a whole lot more!** Install the newest version of Snowpack to get started: ``` $ npm install snowpack@^3.0.0 ``` Or, try out one of our updated [Create Snowpack App](https://www.npmjs.com/package/create-snowpack-app) starter templates: ``` $ npx create-snowpack-app new-project-directory --template @snowpack/app-template-react ``` ## Reimagining Web Development for ESM 1 year ago, Snowpack first released with the mission to reimagine web development for modern JavaScript and ESM. Snowpack leverages modern web features to deliver a frontend build tool that needs just 50ms to start up & react to new file changes, regardless of project size. In comparison, traditional web bundlers could take several seconds or even full minutes to start up in large projects. Snowpack v3.0 marks another huge leap on our mission to push web development forward with the release of **streaming imports**. Streaming imports make it possible to import any package directly into your project, pre-built and pre-bundled for immediate use. It's the power of the entire JavaScript ecosystem, at your fingertips. ## What are Streaming Imports? The typical web developer installs and manages their JavaScript dependencies locally using a package manager CLI like `npm`, `yarn` or `pnpm`. These npm packages can't run directly in the browser, so additional work is needed to resolve, process, build and bundle these packages for the browser before you can actually use them. **What if we could simplify this? What if you could skip the "npm install" step entirely and just fetch the relevant, pre-built package code on-demand via ESM import?** ```js // you do this: import * as React from 'react'; // but get behavior like this: import * as React from 'https://cdn.skypack.dev/react@17.0.1'; ``` That URL in the example above points to [Skypack](https://www.skypack.dev/), a popular JavaScript CDN that we built to serve every package on npm as ESM. Importing dependencies by URL like this is well supported in Snowpack, Deno, and all major browsers. But writing these URLs directly into your source code isn't ideal and makes development impossible without a network connection. **Snowpack v3.0 brings together the best of both worlds:** Get the simplicity of `import 'react'` in your own source code and let Snowpack fetch these dependencies behind the scenes, pre-built and ready to run in the browser. Snowpack caches everything for you automatically, so you can continue to work offline after the first package fetch. This new workflow has several benefits over the traditional "npm install" approach: - **Speed:** Skip the install + build steps for dependencies, and load your dependencies on-demand as pre-build, pre-bundled ESM code. - **Safety:** ESM packages are pre-built into JavaScript for you and never given access to [run code on your machine](https://www.usenix.org/system/files/sec19-zimmermann.pdf). Third-party code only ever runs sandboxed in the browser. - **Less Tooling:** ESM packages are managed by Snowpack, so frontend projects that don't need Node.js (Rails, PHP, etc.) can drop the npm CLI entirely if they choose. - **Identical Final Build:** When you build your site for production, package code is transpiled with the rest of your site and tree-shaken to your exact set of imports. This is our bet on the future of web development. But if this all sounds too wild for you or you have some technical reason to keep managing your dependencies with npm, don't worry. This is **100% opt-in** behavior for those who want it. By default, Snowpack will continue to pull your npm package dependencies out of your project `node_modules` directory like it always has. Check out our guide on [Streaming Package Imports](/guides/streaming-imports) to learn more about how to enable this new behavior in your project today. ![js api](/img/post-snowpackv3-esbuild.png) ## Built-in Optimizations, Powered by esbuild [esbuild](https://esbuild.github.io/) is a marvel: it performs 100x faster than most other popular bundlers their own benchmarks. esbuild is written in Go, a compiled language that can parallelize heavy bundling workloads where other popular bundlers -- written in JavaScript -- cannot. Snowpack already uses esbuild internally as our default single-file builder for JavaScript, TypeScript and JSX files. Snowpack v3.0 takes this integration one step further, with a new built-in build optimization pipeline. Bundle, minify, and transpile your site for production in 1/100th of the time of other bundlers. Snowpack is able to adopt esbuild today thanks to an early bet that we made on the future of bundling: **bundling is just a post-build optimization.** Thanks to this early design decision, esbuild can be plugged in and swapped out of your Snowpack build as easily as any other bundler. esbuild is still a young project, but its future looks promising. In the meantime, we will also continue to invest in the existing bundler plugins for a long time to come, so that more mature projects can continue to use mature bundlers like Webpack & Rollup. To get started, check out the `optimize` option in our newest [Optimizing Your Snowpack Build](/guides/optimize-and-bundle) guide. ![js api](/img/post-snowpackv3-jsapi.png) ## A New JavaScript API Snowpack's new JavaScript API grants you more advanced control over Snowpack's dev server and build pipeline, helping you build more powerful integrations on top of Snowpack to unlock new kinds of dev tooling and server-side rendering (SSR) solutions. [SvelteKit](https://svelte.dev/blog/whats-the-deal-with-sveltekit) is the new official web app framework from the Svelte team, built with Snowpack. SvelteKit uses our new JavaScript API to manage the build pipeline and build files on-demand. Snowpack helps SvelteKit speed up development, with zero rapid updates on file change and zero upfront server start-up cost. [Microsite](https://www.npmjs.com/package/microsite) is another exciting new project built with Snowpack. Microsite is a Static Site Generator (SSG) for Preact that features automatic partial hydration, so that you send as little JavaScript down to the client as possible. Check out our new [JavaScript API reference](/reference/javascript-interface) to start building your own custom integrations on top of Snowpack. ![js api](/img/post-snowpackv3-runtime.png) ## A New Node.js Runtime Speaking of Svelte, this next feature comes directly out of our collaboration with the Svelte team. As a part of building out SvelteKit, Rich Harris created a server-side runtime for Snowpack. This runtime lets you import any Snowpack-built file directly into Node.js, handling things like ESM->CJS conversion and CSS extraction automatically. The result is a unified build pipeline across both Node.js and the frontend, with all of the on-demand build performance benefits of Snowpack. Importing frontend code to run in Node.js unlocks features like true server-side rendering (SSR), test runner integrations for Jest/uvu/Mocha, and more. Check out our new [SSR guide](/guides/server-side-render) to get started and learn more about all of the different ways that you can connect to your Snowpack build.
🥳
## Snowpack's One Year Anniversary Last week marked Snowpack's one-year anniversary of the original v1.0.0 release. Looking back, I'm blown away by everything that's happened since: - 150+ releases (from `v0.0.1`, all the way to v3.0 today) - [100+ Snowpack plugins](https://www.snowpack.dev/plugins) to choose from (and growing fast!) - [100+ individual contributors](https://github.com/withastro/snowpack/graphs/contributors) - [15,000+ stars on GitHub](https://github.com/withastro/snowpack/stargazers) - #1 Developer Productivity Boost Winner, [2020 JS Open Source Awards](https://osawards.com/javascript/2020) - #1 Highest Developer Interest, [2020 State of JS](https://2020.stateofjs.com/en-US/technologies/build-tools/) - #1 Highest Developer Satisfaction (tied), 2020 State of JS A huge thank you to everyone who has contributed code to Snowpack, and the hundreds of developers joining us on GitHub and on [Discord](https://discord.com/invite/snowpack). This project wouldn't exist today without you and your support. Thank you! -- Fred K. Schott [(@FredKSchott)](https://twitter.com/FredKSchott) ================================================ FILE: docs/reference/cli-command-line-interface.md ================================================ --- layout: ../../layouts/content.astro title: Command Line API description: The Snowpack Command Line tool's API, commands, and flags. --- ### Commands ``` $ snowpack --help snowpack init Create a new project config file. snowpack dev Develop your app locally. snowpack build Build your app for production. ... ``` ### Flags ```bash # Show helpful info $ snowpack --help # Show additional debugging logs $ snowpack --verbose # {devOptions: {open: 'none'}} $ snowpack dev --open none # {buildOptions: {clean: true/false}} $ snowpack build --clean $ snowpack build --no-clean ``` **CLI flags will be merged with (and take priority over) your config file values.** Every config value outlined below can also be passed as a CLI flag. Additionally, Snowpack also supports the following flags: - **`--config [path]`** Set the path to your project config file. - **`--help`** Show this help. - **`--version`** Show the current version. - **`--reload`** Clear the local cache. Useful for troubleshooting installer issues. ================================================ FILE: docs/reference/common-error-details.md ================================================ --- layout: ../../layouts/content.astro title: Common Error Details description: How to troubleshoot common issues and error messagesm, plus our resources for getting help. --- This page details several common issues and error messages. For further help we have an active [GitHub Discussion forum](https://github.com/withastro/snowpack/discussions)and [Discord](https://discord.gg/snowpack). Developers and community contributors frequently answer questions on both. ### No such file or directory ``` ENOENT: no such file or directory, open …/node_modules/csstype/index.js ``` This error message would sometimes occur in older versions of Snowpack. **To solve this issue:** Upgrade to Snowpack `v2.6.0` or higher. If you continue to see this unexpected error in newer versions of Snowpack, please file an issue. ### Package exists but package.json "exports" does not include entry Node.js recently added support for a package.json "exports" entry that defines which files you can and cannot import from within a package. Preact, for example, defines an "exports" map that allows you to to import "preact/hooks" but not "preact/some/custom/file-path.js". This allows packages to control their "public" interface. If you see this error message, that means that you've imported a file path not allowed in the export map. If you believe this to be an error, reach out to the package author to request the file be added to their export map. ### Uncaught SyntaxError: The requested module './XXXXXX.js' does not provide an export named 'YYYYYY' If you are using TypeScript, this error could occur if you are importing or exporting something that only exists in TypeScript (like a type or interface) and doesn't actually exist in the final JavaScript code. Our built-in TypeScript support can detect type-only imports and will attempt to remove them automatically. This is however much more difficult for type-only export statements, because Snowpack cannot detect that an exported symbol is a type without keeping context across multiple files. For example, a statement such as `export { MyInterfaceName }` will not work in Snowpack. **To solve:** Enable [`isolatedModules`](https://www.typescriptlang.org/tsconfig#isolatedModules) in `tsconfig.json` to identify problematic cases. Use `import type { MyInterfaceName }` and `export type { MyInterfaceName }` to help Snowpack ignore types. This error could also appear if named imports are used with older, Common.js npm packages. Thanks to improvements in our package scanner this is no longer a common issue for most packages. However, some packages are written or compiled in a way that makes automatic import scanning impossible. **To solve:** Use the default import (`import pkg from 'my-old-package'`) for legacy Common.js/UMD packages that cannot be analyzed. Or, add the package name to your `packageOptions.namedExports` configuration for runtime import scanning. ```js // snowpack.config.mjs export default { packageOptions: { namedExports: ['@shopify/polaris-tokens'], }, }; ``` ### Installing Non-JS Packages When installing packages from npm, you may encounter some file formats that can run only with additional parsing/processing. First check to see if there is a [Snowpack plugin for the type of file](/plugins). Because our internal installer is powered by Rollup, you can also add Rollup plugins to your [Snowpack config](/reference/configuration) to handle these special, rare files: ```diff // snowpack.config.mjs export default { + rollup: { + plugins: [require('rollup-plugin-sass')()], + }, }; ``` Refer to [Rollup’s documentation on plugins](https://rollupjs.org/guide/en/#using-plugins) for more information. ### RangeError: Invalid WebSocket frame: RSV1 must be clear **To solve this issue:** Use any other port than `8080` for the dev server. To do so, specify a port in your [Snowpack config](/reference/configuration): ```diff // snowpack.config.mjs export default { + devOptions: { + port: 3000, + }, }; ``` ### Package "[name]" not found. Have you installed it? This warning appears when Snowpack believes something to be in `node_modules`, but can’t find it. This typically happens because you‘ve tried to import something without a leading `/`, `./`, or `../`. Here are some possible fixes: #### I’m trying to import a file from npm If you were trying to import an npm package, try running the following: ``` npm install [package]. ``` Then re-running Snowpack. If the issue still persists, try telling Snowpack where to find this with [an alias](https://www.snowpack.dev/reference/configuration#alias): ```diff // snowpack.config.mjs export default { + alias: { + myPackage: './path/to/myPackage', + }, }; ``` #### I’m trying to import a local `.js` file If you‘re getting this error while trying to import a local file, the fix usually looks like this: ```diff - import myFile from 'myFile.js'; + import myFile from './myFile.js'; ``` If the issue still persists, [please open an issue](https://github.com/withastro/snowpack/issues/new/choose). #### I’m trying to import a local `.css` file The fix for `.css` files is similar to JS. Prefix a `./` to the import path like so: ```diff - @import "myfile.css"; + @import "./myfile.css"; ``` While it’s true you may be used to writing CSS without the `./`, and a browser respects it, Snowpack works a little differently. Because we let you import from npm seamlessly, `myfile.css` will be resolved as an npm package whereas `/myfile.css`, `./myfile.css`, and `../myfile.css` will be resolved as local project files. Also, `./myfile.css` is perfectly-valid, and it’s good to get in the habit of using it consistently to prevent ambiguous resolution. ================================================ FILE: docs/reference/configuration.md ================================================ --- layout: ../../layouts/content.astro title: snowpack.config.js description: The Snowpack configuration API reference. --- ```js // Example: snowpack.config.mjs // The added "@type" comment will enable TypeScript type information via VSCode, etc. /** @type {import("snowpack").SnowpackUserConfig } */ export default { plugins: [ /* ... */ ], }; ``` To generate a basic configuration file scaffold in your Snowpack project run `snowpack init`. ## mode **Type**: `"test" | "development" | "production"` **Default**: `"development"` for `snowpack dev`, `"production"` for `snowpack build`. Specifies the "mode" that Snowpack should run in. The main impact of this is the value of `import.meta.env.MODE` at runtime, although there are some other key differences between modes: - `"test"`: `testOptions.files` are not excluded, and will be scanned & built as normal source files. Useful when running tests on top of Snowpack. ## root **Type**: `string` **Default**: `/` Specify the root of a project using Snowpack. (Previously: `config.cwd`) ## workspaceRoot **Type**: `string` Specify the root of your workspace or monorepo, if you are using one. When configured, Snowpack will treat any sibling packages in your workspace like source files, and pass them through your unbundled Snowpack build pipeline during development. This allows for fast refresh, HMR support, file change watching, and other dev improvements when working in monorepos. When you build your site for production, symlinked packages will be treated like any other package, bundled and tree-shaken into single files for faster loading. ## install Deprecated! Moved to `packageOptions.knownEntrypoints` ## extends **Type**: `string` Inherit from a separate "base" config. Can be a relative file path, an npm package, or a file within an npm package. Your configuration will be merged on top of the extended base config. ## exclude **Type**: `string[]` **Default**: `['**/node_modules/**/*']` Exclude any files from the Snowpack pipeline. Supports glob pattern matching. ## mount ``` mount: { [path: string]: string | {url: string, resolve: boolean, static: boolean, dot: boolean} } ``` Mount local directories to custom URLs in your built application. - `mount.url` | `string` | _required_ : The URL to mount to, matching the string in the simple form above. - `mount.static` | `boolean` | _optional_ | **Default**: `false` : If true, don't build files in this directory. Copy and serve them directly from disk to the browser. - `mount.resolve` | `boolean` | _optional_ | **Default**: `true`: If false, don't resolve JS & CSS imports in your JS, CSS, and HTML files. Instead send every import to the browser, as written. - `mount.dot` | `boolean` | _optional_ | **Default**: `false`: If true, include dotfiles (ex: `.htaccess`) in the final build. Example: ```js // snowpack.config.mjs // Example: Basic "mount" usage export default { mount: { src: '/dist', public: '/', }, }; ``` You can further customize this the build behavior for any mounted directory by using the expanded object notation: ```js // snowpack.config.mjs // Example: expanded object notation "mount" usage export default { mount: { // Same behavior as the "src" example above: src: {url: '/dist'}, // Mount "public" to the root URL path ("/*") and serve files with zero transformations: public: {url: '/', static: true, resolve: false}, }, }; ``` ## env **Type**: `Record` Declare any environment variables that should be exposed on `import.meta.env` at runtime. See [Environment Variables](/reference/environment-variables) for more information. ```js // snowpack.config.mjs export default { env: { API_URL: 'api.google.com', }, }; ``` ## alias **Type**: `object` (package: package or path) Configure import aliases for directories and packages. Note: In an older version of Snowpack, all mounted directories were also available as aliases by **Default**. As of Snowpack 2.7, this is no longer the case and no aliases are defined by **Default**. ```js // snowpack.config.mjs // Example: alias types export default { alias: { // Type 1: Package Import Alias lodash: 'lodash-es', react: 'preact/compat', // Type 2: Local Directory Import Alias (relative to cwd) components: './src/components', '@app': './src', }, }; ``` ## plugins **Type**: `array` containing pluginName `string` or an array [`pluginName`, {`pluginOptions`} Enable Snowpack plugins and their options. Also see our [Plugin guide](/guides/plugins) ```js // snowpack.config.mjs // Example: enable plugins both simple and expanded export default { plugins: [ // Simple format: no options needed 'plugin-1', // Expanded format: allows you to pass options to the plugin ['plugin-2', {'plugin-option': false}], ]; } ``` ## devOptions **Type**: `object` (option name: value) Configure the Snowpack dev server. ### devOptions.secure **Type**: `boolean` or `object` **Default**: `false` Toggles whether Snowpack dev server should use HTTPS with HTTP2 enabled. See the [SSL Certificates](/guides/https-ssl-certificates) Guide for more information. If the value is `true`, Snowpack will look for a `snowpack.crt` and `snowpack.key` file in your `root` directory. If the value is an `object`, you may pass your custom `cert` and `key` files directly to it. ```js // snowpack.config.mjs import fs from 'fs'; const cert = await fs.promises.readFile('/path/to/server.crt'); const key = await fs.promises.readFile('/path/to/server.key'); export default { devOptions: { secure: {cert, key}, }, }; ``` ### devOptions.hostname **Type**: `string` **Default**: `localhost` The hostname that the dev server is running on. Snowpack uses this information to configure the HMR websocket and properly open your browser on startup (see: [`devOptions.open`](#devoptions.open)). ### devOptions.port **Type**: `number` **Default**: `8080` The port the dev server runs on. ### devOptions.openUrl **Type**: `string` Optional path to append to dev server url. May also include querystring parameters, example: `test/foo.html?bar=123`. ### devOptions.open **Type**: `string` **Default**: `"**Default**"` Configures how the dev server opens in the browser when it starts. Any installed browser, e.g., "chrome", "firefox", "brave", or the path to a browser. Set "none" to disable. ### devOptions.output **Type**: `"stream" | "dashboard"` **Default**: `"dashboard"` Set the output mode of the `dev` console: - `"dashboard"` delivers an organized layout of console output and the logs of any connected tools. This is recommended for most users and results in the best logging experience. - `"stream"` is useful when Snowpack is run in parallel with other commands, where clearing the shell would clear important output of other commands running in the same shell. ### devOptions.hmr **Type**: `boolean` **Default**: `true` Toggles HMR on the Snowpack dev server. ### devOptions.hmrDelay **Type**: `number` (milliseconds) **Default**: `0` Milliseconds to delay HMR-triggered browser update. ### devOptions.hmrPort **Type**: `number` **Default**: [`devOptions.port`](#devoptions.port) The port where Snowpack's HMR Websocket runs. ### devOptions.hmrErrorOverlay **Type**: `boolean` **Default**: `true` Toggles a browser overlay that displays JavaScript runtime errors when running HMR. ### devOptions.out **Type**: `string` **Default**: `"build"` _NOTE:_ Deprecated, see `buildOptions.out`. ### devOptions.tailwindConfig **Type**: `string` If using Tailwind, specify the path to your config file. e.g.: `tailwindConfig: './tailwind.config.js'` ## installOptions **Type**: `object` _NOTE:_ Deprecated, see `packageOptions`. ## packageOptions **Type**: `object` Configure how npm packages are installed and used. ### packageOptions.external **Type**: `string[]` **Example**: `"external": ["fs"]` Mark some imports as external. Snowpack will ignore these imports and leave them as-is in your final build. This is an advanced feature: Bare imports are not supported in any major browser, so an ignored import will usually fail when sent directly to the browser. This will most likely fail unless you have a specific use-case that requires it. ### packageOptions.source **Type**: `"local" | "remote"` **Default**: `"local"` **Example**: `"source": "local"` Your JavaScript npm packages can be consumed in two different ways: **local** and **remote**. Each mode supports a different set of package options. You can choose between these two different modes by setting the `packageOptions.source` property. ### packageOptions.source=local Load your dependencies from your local `node_modules/` directory. Install and manage your dependencies using `npm` (or any other npm-ready package manager) and a project `package.json` file. This is traditional Snowpack behavior matching Snowpack v2. This mode is recommended for anyone already using npm to manage their frontend dependencies. #### packageOptions.knownEntrypoints **Type**: `string[]` Known dependencies to install with Snowpack. Used for installing packages any dependencies that cannot be detected by our automatic import scanner (ex: package CSS files). #### packageOptions.polyfillNode **Type**: `boolean` **Default**: `false` This will automatically polyfill any Node.js dependencies as much as possible for the browser Converts packages that depend on Node.js built-in modules (`"fs"`, `"path"`, `"url"`, etc.). You can see the full list of supported polyfills at the [rollup-plugin-node-polyfills documentation](https://github.com/ionic-team/rollup-plugin-node-polyfills) If you'd like to customize this polyfill behavior, you can provide your own Rollup plugin for the installer: ```js // snowpack.config.mjs // Example: If `--polyfill-node` doesn't support your use-case, you can provide your own custom Node.js polyfill behavior import rollupPluginNodePolyfills from 'rollup-plugin-node-polyfills'; export default { packageOptions: { polyfillNode: false, rollup: { plugins: [rollupPluginNodePolyfills({crypto: true, ...})], }, }, }; ``` When `source="remote"`, Node.js polyfills are always provided. Configuring this option is only supported in `source="local"` mode. #### packageOptions.env **Type**: `{[ENV_NAME: string]: (string true)}` Sets a `process.env.` environment variable inside the installed dependencies. If set to true (ex: `{NODE_ENV: true}` or `--env NODE_ENV`) this will inherit from your current shell environment variable. Otherwise, set to a string (ex: `{NODE_ENV: 'production'}` or `--env NODE_ENV=production`) to set the exact value manually. This option is only supported in `source="local"` mode. `source="remote"` does not support this feature yet. #### packageOptions.packageLookupFields **Type**: `string[]` **Example**: `"packageLookupFields": ["svelte"]` Set custom lookup fields for dependency `package.json` file entrypoints, in addition to the defaults like "module", "main", etc. This option is only supported in `source="local"` mode. `source="remote"` does not support this feature yet. #### packageOptions.packageExportLookupFields **Type**: `string[]` **Example**: `"packageExportLookupFields": ["svelte"]` Set custom lookup fields for dependency `package.json` ["exports" mappings.](https://nodejs.org/api/packages.html#packages_package_entry_points) This option is only supported in `source="local"` mode. `source="remote"` does not support this feature yet. #### packageOptions.rollup **Type**: `Object` Allows customization of Snowpack's internal Rollup configuration. Snowpack uses Rollup internally to install your packages. This `rollup` config option gives you deeper control over the internal Rollup configuration that we use. - packageOptions.rollup.plugins | `RollupPlugin[]` - Provide an array of custom Rollup plugins that will run on every installed package. Useful for dealing with non-standard file types in your npm packages. - packageOptions.rollup.dedupe | `string[]` - If needed, deduplicate multiple versions/copies of a packages to a single one. This helps prevent issues with some packages when multiple versions are installed from your node_modules tree. See [rollup-plugin-node-resolve](https://github.com/rollup/plugins/tree/master/packages/node-resolve#usage) for more documentation. - packageOptions.rollup.context | `string` - Specify top-level `this` value. Useful to silence install errors caused by legacy common.js packages that reference a top-level this variable, which does not exist in a pure ESM environment. Note that the `'THIS_IS_UNDEFINED'` warning ("'this' keyword is equivalent to 'undefined' ... and has been rewritten") is silenced by default, unless `--verbose` is used. This option is only supported in `source="local"` mode. `source="remote"` does not support custom Rollup install options. ### packageOptions.source=remote Enable streaming package imports. Load dependencies from our remote CDN. Manage your dependencies using `snowpack` and a project `snowpack.deps.json` file. [Learn more about Streaming Remote Imports](/guides/streaming-imports). #### packageOptions.origin **Type**: `string` **Default**: `https://pkg.snowpack.dev` The remote origin to import packages from. When you import a new package, Snowpack will fetch those resources from this URL. Currently, the origin must implement a specific response format that Snowpack can parse for ESM. In future versions of Snowpack we plan to add support for custom CDNs and import origins. #### packageOptions.cache **Type**: `string` **Default**: `.snowpack` The location of your project cache folder, relative to the project root. Snowpack will save cached data to this folder. For example, if `packageOptions.types` is set to true, Snowpack will save TypeScript types to a `types` directory within this folder. #### packageOptions.types **Type**: `boolean` **Default**: `false` If true, Snowpack will download TypeScript types for every package. ## buildOptions **Type**: `object` (option name: value) Configure your final build. ### buildOptions.out **Type**: `string` **Default**: `"build"` The local directory that we output your final build to. ### buildOptions.baseUrl **Type**: `string` **Default**: `/` In your HTML, replace all instances of `%PUBLIC_URL%` with this Inspired by the same [Create React App](https://create-react-app.dev/docs/using-the-public-folder/) concept. This is useful if your app will be deployed to a subdirectory. ### buildOptions.clean **Type**: `boolean` **Default**: `true` Set to `false` to prevent Snowpack from deleting the build output folder (`buildOptions.out`) between builds. ### buildOptions.cacheDirPath **Type**: `string` **Default**: `./node_modules/.cache/snowpack` Specify the cache directory in which bundled Node modules will be cached. ### buildOptions.webModulesUrl _NOTE:_ Deprecated, see `buildOptions.metaUrlPath`. ### buildOptions.metaDir _NOTE:_ Deprecated, see `buildOptions.metaUrlPath`. ### buildOptions.metaUrlPath **Type**: `string` **Default**: `_snowpack` Rename the default directory for Snowpack metadata. In every build, Snowpack creates meta files for loading things like [HMR](/concepts/hot-module-replacement), [Environment Variables](/reference/environment-variables), and your built npm packages. When you build your project, this will be a path on disk relative to the `buildOptions.out` directory. ### buildOptions.sourcemap **Type**: `boolean` **Default**: `false` Generates source maps. **_Experimental:_** Still in progress, you may encounter some issues when using source maps until this support is finalized. ### buildOptions.watch **Type**: `boolean` **Default**: `false` Run Snowpack's build pipeline through a file watcher. This option works best for local development when you have a custom frontend server (ex: Rails, PHP, etc.) and the Snowpack dev server cannot be used. ### buildOptions.htmlFragments **Type**: `boolean` **Default**: `false` Toggles whether HTML fragments are transformed like full HTML pages. HTML fragments are HTML files not starting with ``. ### buildOptions.jsxFactory **Type**: `string` **Default**: `React.createElement` (or `h` if Preact import is detected) Set the name of the function used to create JSX elements. ### buildOptions.jsxFragment **Type**: `string` **Default**: `React.Fragment` (or `Fragment` if Preact import is detected) Set the name of the function used to create JSX fragments. ### buildOptions.jsxInject **Type**: `string` **Default**: `undefined` If set, this string can be used to automatically inject JSX imports for every JSX/TSX file. React users might use `import React from 'react'` whereas Preact users might use `import { h, Fragment } from 'preact'`. ## testOptions Configure your tests. ### testOptions.files **Type**: `string[]` **Default**: `["__tests__/**/*", "**/*.@(spec|test).*"]` Specifies your test files. If `NODE_ENV` is set to "test", Snowpack includes these files in your site build and scan them for installable dependencies. Otherwise, Snowpack excludes these files. ## experiments **Type**: `object` (option name: value) This section is currently empty! In the future, this section may be used for experimental and not yet finalized. ================================================ FILE: docs/reference/environment-variables.md ================================================ --- layout: ../../layouts/content.astro title: Environment Variables description: Using environment variables with Snowpack --- For your safety, Snowpack supports only environment variables which begin with `SNOWPACK_PUBLIC_*`. We do this because everything in your web application is sent to the browser, and we don't want you to accidentally share sensitive keys/env variables with your public web application. Prefixing your frontend web env variables with `SNOWPACK_PUBLIC_` is a good reminder that they will be shared with the world. ## Setting environment variables You can set environment variables with snowpack in three different ways: ### Option 1: CLI Set environment variables when you run the snowpack CLI: ```bash SNOWPACK_PUBLIC_API_URL=api.google.com snowpack dev ``` ### Option 2: Config file **New in v3.1.0** Pass environment variables as an object to the `env` property. Note that these environment variables do not need to use the `SNOWPACK_PUBLIC_` prefix and anything set here will be available on `import.meta.env` (see below). ```js // snowpack.config.mjs export default { env: { API_URL: 'api.google.com', }, }; ``` **In prior versions**, we recommended setting environment variables by adding to `process.env.*` at the top of your `snowpack.config.mjs` file. This ended up being pretty confusing, so using the `env` property is now the recommended approach. ```js // snowpack.config.mjs process.env.SNOWPACK_PUBLIC_API_URL = 'api.google.com'; // ...rest of config ``` ### Option 3: Plugin Use a plugin such as [plugin-dotenv](https://www.npmjs.com/package/@snowpack/plugin-dotenv) to load environment variables from a `.env` file. ## Reading environment variables You can read environment variables directly in your web application via `import.meta.env`. If you've ever used `process.env` in Create React App or any Webpack application, this behaves exactly the same. ```js // `import.meta.env` - Read process.env variables in your web app fetch(`${import.meta.env.SNOWPACK_PUBLIC_API_URL}/users`).then(...) // Supports destructuring as well: const {SNOWPACK_PUBLIC_API_URL} = import.meta.env; fetch(`${SNOWPACK_PUBLIC_API_URL}/users`).then(...) // Instead of `import.meta.env.NODE_ENV` use `import.meta.env.MODE` if (import.meta.env.MODE === 'development') { // ... ``` `import.meta.env.MODE` and `import.meta.env.NODE_ENV` are also both set to the current `process.env.NODE_ENV` value, so that you can change app behavior based on dev vs. build. The env value is set to `development` during `snowpack dev`, and `production` during `snowpack build`. Use this in your application instead of `process.env.NODE_ENV`. You can also use environment variables in HTML files. All occurrences of `%SNOWPACK_PUBLIC_*%`, `%PUBLIC_URL%`, and `%MODE%` will be replaced at build time. **Remember:** that these env variables are statically injected into your application for everyone at **build time**, and not runtime. ================================================ FILE: docs/reference/hot-module-replacement.md ================================================ --- layout: ../../layouts/content.astro title: Hot Module Replacement (HMR) API description: Snowpack implements HMR via the esm-hmr spec, an attempted standard for ESM-based Hot Module Replacement (HMR). --- Snowpack implements HMR via the [esm-hmr](https://github.com/pikapkg/esm-hmr) spec, an attempted standard for ESM-based Hot Module Replacement (HMR). ```js // HMR Code Snippet Example if (import.meta.hot) { import.meta.hot.accept(({module}) => { // Accept the module, apply it into your application. }); } ``` Full API Reference: [snowpack/esm-hmr on GitHub](https://github.com/snowpackjs/esm-hmr) [Learn more](/concepts/hot-module-replacement) about HMR, Fast Refresh, and how it's meant to work in Snowpack. ================================================ FILE: docs/reference/javascript-interface.md ================================================ --- layout: ../../layouts/content.astro title: JavaScript API description: Snowpack's JavaScript API is for anyone who wants to integrate with some custom build pipeline or server-side rendering engine. --- Most users will interact with Snowpack via the [command-line](/reference/cli-command-line-interface) interface (CLI). However, Snowpack also ships a JavaScript API for anyone to build on top of. This page contains reference information on Snowpack's public API and all related data types. A full set of all data types defined within the project (public and private) can be found in [the package's `types.d.ts` file](https://unpkg.com/browse/snowpack@3.0.10/lib/types.d.ts). ### createConfiguration() `createConfiguration(config?: SnowpackUserConfig) => SnowpackConfig` ```js import {createConfiguration} from 'snowpack'; const config = createConfiguration({...}); ``` Almost everything that you do with Snowpack requires a configuration object. Snowpack is designed to work with zero config, and the `config` argument that this function takes can be full, empty, or only contain a couple of properties. The rest of the configuration object will be filled out with Snowpack's usual set of defaults, outlined in our [snowpack.config.mjs documentation.](/reference/configuration). The easiest way to think about the difference is that `SnowpackUserConfig` is the externally-documented configuration format, and `SnowpackConfig` is our internal representation with all optional/undefined values populated with the actual defaults. ### loadConfiguration() `loadConfiguration(overrides?: SnowpackUserConfig, configPath?: string | undefined) => Promise` ```js import {loadConfiguration} from 'snowpack'; const config = await loadConfiguration({...}, '/path/to/snowpack.config.mjs'); ``` Similar to `createConfiguration`, but this function will actually check the file system to load a configuration file from disk. All paths within that configuration file are relative to the file itself. ### startServer() `function startServer({config: SnowpackUserConfig}) => Promise` ```js import {startServer} from 'snowpack'; const config = createConfiguration({...}); const server = await startServer({config}); // returns: SnowpackDevServer ``` Start a new Snowpack dev server instance. This is the equivalent of running `snowpack dev` on the command line. Once started, you can load files from your dev server and Snowpack will build them as requested. This is an important feature to understand: Snowpack's dev server does zero file building on startup, and instead builds files only once they are requested via the server's `loadUrl` method. ### SnowpackDevServer #### SnowpackDevServer.port The port that the server is listening on. #### SnowpackDevServer.loadUrl() `loadUrl(reqUrl: string, opt?: {isSSR?: boolean; allowStale?: boolean; encoding?: string}): Promise>;` ```ts const server = await startServer({config}); const {contents} = server.loadUrl('/dist/index.js', {...}); ``` Load a file and return the result. On the first request of a URL, this will kick off a build that will then be cached for all future requests during the life of the server. You can pass `allowStale: true` to enable Snowpack's cold cache for cached results from past sessions. However, Snowpack provides no guarentee on the freshness of the cold-cache data. #### SnowpackDevServer.getUrlForFile() `getUrlForFile(fileLoc: string) => string | null;` ```ts const server = await startServer({config}); const fileUrl = server.getUrlForFile('/path/to/index.jsx'); const {contents} = server.loadUrl(fileUrl, {...}); ``` A helper function to find the final hosted URL for any source file. Useful when combined with `loadUrl`, since you may only know a file's location on disk without knowing it's final hosted URL. #### SnowpackDevServer.getUrlForPackage() `getUrlForPackage(packageSpec: string) => Promise` ```ts const server = await startServer({config}); const pkgUrl = await server.getUrlForPackage('preact'); ``` A helper function to find the final hosted URL of any dependency. #### SnowpackDevServer.sendResponseError() `sendResponseError(req: http.IncomingMessage, res: http.ServerResponse, status: number) => void;` A helper function to send an error response in a server response handler. Useful when integrating Snowpack with Express, Koa, or any other Node.js server. #### SnowpackDevServer.onFileChange() `onFileChange({filePath: string}) => void;` Listen for watched file change events. Useful for situations where you might want to watch the file system for changes yourself, and can save overhead/performance by hooking into our already-running watcher. #### SnowpackDevServer.shutdown() `shutdown() => Promise;` ```ts const server = await startServer({config}); await server.shutdown(); ``` Shut down the Snowpack dev server. Cleanup any long-running commands, file watchers, etc. #### SnowpackDevServer.getServerRuntime() `getServerRuntime({invalidateOnChange?: boolean}) => ServerRuntime;` ```ts const server = await startServer({config}); const runtime = server.getServerRuntime(); const {helloWorld} = (await runtime.importModule('/dist/index.js')).exports; helloWorld(); ``` Returns an ESM Server Runtime that lets Node.js import modules directly out of Snowpack's build cache. Useful for SSR, test running frontend code, and the overall unification of your build pipeline. For more information, check out our guide on [Server-Side Rendering](/guides/server-side-render) using the `getServerRuntime()` API. #### ServerRuntime ```ts interface ServerRuntime { /** Import a Snowpack-build JavaScript file into Node.js. */ importModule(url: string) => Promise; /** Invalidate a module in the internal runtime cache. */ invalidateModule(url: string) => void; } ``` #### ServerRuntimeModule ```ts interface ServerRuntimeModule { /** The imported module. */ exports: any; /** References to all internal CSS imports. Useful for CSS extraction. */ css: string[]; } ``` ### build() `build({config: SnowpackUserConfig}) => Promise` ```js import {build} from 'snowpack'; const config = createConfiguration({...}); const {result} = await build({config}); // returns: SnowpackBuildResult ``` #### SnowpackBuildResult.result An in-memory manifest of all build inputs & output files. #### SnowpackBuildResult.shutdown In `--watch` mode, the `build()` function will resolve but the build itself will continue. Use this function to shut down the build watcher. In normal build mode (non-watch mode) this function will throw with a warning. #### SnowpackBuildResult.onFileChange In `--watch` mode, the `build()` function will resolve but the build itself will continue. Use this function to respond to file change events without having to spin up your own file watcher. In normal build mode (non-watch mode) this function will throw with a warning. ### getUrlForFile() `getUrlForFile(fileLoc: string, config: SnowpackConfig) => string | null` ```js import {getUrlForFile} from 'snowpack'; const fileUrl = getUrlForFile('/path/to/file.js', config); ``` A helper function to find the final hosted URL for any source file. Useful when combined with `loadUrl`, since you may only know a file's location on disk without knowing it's final hosted URL. Similar to `SnowpackDevServer.getUrlForFile()`, but requires a second `config` argument to inform the result. ### clearCache() `clearCache() => Promise` ```js import {clearCache} from 'snowpack'; await clearCache(); ``` Equivalent of using the `--reload` flag with the `snowpack` CLI. Clears all cached data in Snowpack. Useful for troubleshooting, or clearing the cache after making some change that Snowpack couldn't detect. ### logger ```js import {logger} from 'snowpack'; ``` You can control Snowpack's internal logger directly by importing it. Note that this is an advanced feature not needed for most users. Instead, use the `verbose` config option to enable debug logging and control log message verbosity. ================================================ FILE: docs/reference/plugins.md ================================================ --- layout: ../../layouts/content.astro title: Plugin API description: The Snowpack Plugin API and how to use it. --- Looking to get started writing your own plugin? Check out our [Plugin Guide](/guides/plugins) for an overview of how plugins work and a walk-through to help you create your own. Looking for a good summary? Check out our ["SnowpackPlugin" TypeScript definition](https://github.com/withastro/snowpack/blob/main/snowpack/src/types.ts#L130) for a fully documented and up-to-date overview of the Plugin API and all supported options. ### Overview ```js // my-first-snowpack-plugin.js module.exports = function (snowpackConfig, pluginOptions) { return { name: 'my-first-snowpack-plugin', config() { console.log('Success!'); }, }; }; // To use this plugin, add it to your snowpack.config.mjs: // // export default { // plugins: [ // ["./my-first-snowpack-plugin.js", {/* pluginOptions */ }], // ], // }; ``` A **Snowpack Plugin** is an object interface that lets you customize Snowpack's behavior. Snowpack provides different hooks for your plugin to connect to. For example, you can add a plugin to handle Svelte files, optimize CSS, convert SVGs to React components, run TypeScript during development, and much more. Snowpack's plugin interface is inspired by [Rollup](https://rollupjs.org/). If you've ever written a Rollup plugin before, then hopefully these concepts and terms feel familiar. ### Lifecycle Hooks #### config() ```js config(snowpackConfig) { // modify or read from the Snowpack configuration object } ``` Use this hook to read or make changes to the completed Snowpack configuration object. This is currently the recommended way to access the Snowpack configuration, since the one passed to the top-level plugin function is not yet finalized and may be incomplete. #### load() Load a file from disk and build it for your application. This is most useful for taking a file type that can't run in the browser (TypeScript, Sass, Vue, Svelte) and returning JS and/or CSS. It can even be used to load JS/CSS files directly from disk with a build step like Babel or PostCSS. #### transform() Transform a file's contents. Useful for making changes to all types of build output (JS, CSS, etc.) regardless of how they were originally loaded from disk. #### run() Run a CLI command, and connect it's output into the Snowpack console. Useful for connecting tools like TypeScript. #### optimize() Snowpack’s bundler plugin API is still experimental and may change in a future release. See our official bundler plugins for an example of using the current interface: - Example: [@snowpack/plugin-webpack](https://github.com/withastro/snowpack/tree/main/plugins/plugin-webpack) - Example: [snowpack-plugin-rollup-bundle](https://github.com/ParamagicDev/snowpack-plugin-rollup-bundle) #### onChange() Get notified any time a watched file changes. This can be useful when paired with the `markChanged()` plugin method, to mark multiple files changed at once. See [@snowpack/plugin-sass](https://github.com/withastro/snowpack/tree/main/plugins/plugin-sass/plugin.js) for an example of how to use this method. ### Plugin Properties #### knownEntrypoints ``` // Example: Svelte plugin needs to make sure this dependency can be loaded. knownEntrypoints: ["svelte/internal"] ``` A list of any npm dependencies that are added as a part of `load()` or `transform()` that Snowpack will need to know about. Snowpack analyzes most dependency imports automatically when it scans the source code of a project, but some imports are added as a part of a `load()` or `transform()` step, which means that Snowpack would never see them. If your plugin does this, add them here. #### resolve ``` // Example: Sass plugin compiles Sass files to CSS. resolve: {input: [".sass"], output: [".css"]} // Example: Svelte plugin compiles Svelte files to JS & CSS. resolve: {input: [".svelte"], output: [".js", ".css"]} ``` If your plugin defines a `load()` method, Snowpack will need to know what files your plugin is responsible to load and what its output will look like. **`resolve` is needed only if you also define a `load()` method.** - `input`: An array of file extensions that this plugin will load. - `output`: The set of all file extensions that this plugin's `load()` method will output. - [Full TypeScript definition](https://github.com/withastro/snowpack/tree/main/snowpack/src/types/snowpack.ts). ### Plugin Methods #### this.markChanged() ```js // Called inside any plugin hooks this.markChanged('/some/file/path.scss'); ``` Manually mark a file as changed, regardless of whether the file changed on disk or not. This can be useful when paired with the `markChanged()` plugin hook, to mark multiple files changed at once. - See [@snowpack/plugin-sass](https://github.com/withastro/snowpack/tree/main/plugins/plugin-sass/plugin.js) for an example of how to use this method. - [Full TypeScript definition](https://github.com/withastro/snowpack/blob/main/snowpack/src/types.ts). ================================================ FILE: docs/reference/supported-files.md ================================================ --- layout: ../../layouts/content.astro title: Supported Files description: Snowpack ships with built-in support for many file types including json, js, ts, jsx, css, css modules, and images. --- Snowpack ships with built-in support for the following file types, no configuration required: - JavaScript (`.js`, `.mjs`) - TypeScript (`.ts`, `.tsx`) - JSON (`.json`) - JSX (`.jsx`, `.tsx`) - CSS (`.css`) - CSS Modules (`.module.css`) - Images & Assets (`.svg`, `.jpg`, `.png`, etc.) - WASM (`.wasm`) To customize build behavior and support new languages [check out our tooling guide](/guides/connecting-tools) ### JavaScript & ESM Snowpack was designed to support JavaScript's native ES Module (ESM) syntax. ESM lets you define explicit imports & exports that browsers and build tools can better understand and optimize for. If you're familiar with the `import` and `export` keywords in JavaScript, then you already know ESM! ```js // ESM Example - src/user.js export function getUser() { /* ... */ } // src/index.js import {getUser} from './user.js'; ``` All modern browsers support ESM, so Snowpack is able to ship this code directly to the browser during development. This is what makes Snowpack's **unbundled development** workflow possible. Snowpack also lets you import non-JavaScript files directly in your application. Snowpack handles all this for you automatically so there's nothing to configure, using the following logic: ### TypeScript Snowpack includes built-in support to build TypeScript files (`*.ts`) to JavaScript. Note that this built-in support is build only. By default, Snowpack does not type-check your TypeScript code. To integrate type checking into your development/build workflow, add the [@snowpack/plugin-typescript](https://www.npmjs.com/package/@snowpack/plugin-typescript) plugin. ### JSX Snowpack includes built-in support to build JSX files (`*.jsx` & `*.tsx`) to JavaScript. If you are using Preact, Snowpack will detect this and switch to use the Preact-style JSX `h()` function. This is all done automatically for you. If you need to customize this behavior, consider adding the [@snowpack/plugin-babel](https://www.npmjs.com/package/@snowpack/plugin-babel) plugin for full compiler customization via Babel. **Note: Snowpack's default build does not support JSX in `.js`/`.ts` files.** If you can't use the `.jsx`/`.tsx` file extension, you can use [@snowpack/plugin-babel](https://www.npmjs.com/package/@snowpack/plugin-babel) to build your JavaScript instead. ### JSON ```js // Load the JSON object via the default export import json from './data.json'; ``` Snowpack supports importing JSON files directly into your application. Imported files return the full JSON object in the default import. ### CSS ```js // Load and inject 'style.css' onto the page import './style.css'; ``` Snowpack supports importing CSS files directly into your application. Imported styles expose no exports, but importing one will automatically add those styles to the page. This works for all CSS files by default, and can support compile-to-CSS languages like Sass & Less via plugins. If you prefer not to write CSS, Snowpack also supports all popular CSS-in-JS libraries (ex: styled-components) for styling. ### CSS Modules ```js // 1. Converts './style.module.css' classnames to unique, scoped values. // 2. Returns an object mapping the original classnames to their final, scoped value. import styles from './style.module.css'; // This example uses JSX, but you can use CSS Modules with any framework. return
Your Error Message
; ``` Snowpack supports CSS Modules using the `[name].module.css` naming convention. Like any CSS file, importing one will automatically apply that CSS to the page. However, CSS Modules export a special default `styles` object that maps your original classnames to unique identifiers. CSS Modules help you enforce component scoping & isolation on the frontend with unique-generated class names for your stylesheets. ### Other Assets ```jsx import imgReference from './image.png'; // img === '/src/image.png' import svgReference from './image.svg'; // svg === '/src/image.svg' import txtReference from './words.txt'; // txt === '/src/words.txt' // This example uses JSX, but you can use import references with any framework. ; ``` All other assets not explicitly mentioned above can be imported via ESM `import` and will return a URL reference to the final built asset. This can be useful for referencing non-JS assets by URL, like creating an image element with a `src` attribute pointing to that image. ### WASM ```js // Loads and intializes the requested WASM file const wasm = await WebAssembly.instantiateStreaming(fetch('/example.wasm')); ``` Snowpack supports loading WASM files directly into your application using the browser's [`WebAssembly`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly) API. Read our [WASM guide](/guides/wasm) to learn more. ### Import NPM Packages ```js // Returns the React & React-DOM npm packages import React from 'react'; import ReactDOM from 'react-dom'; ``` Snowpack lets you import npm packages directly in the browser. Even if a package was published using a legacy format, Snowpack will up-convert the package to ESM before serving it to the browser. When you start up your dev server or run a new build, you may see a message that Snowpack is "installing dependencies". This means that Snowpack is converting your dependencies to run in the browser. This needs to run only once, or until you next change your dependency tree by adding or removing dependencies. ================================================ FILE: docs/tutorials/getting-started.md ================================================ --- layout: ../../layouts/content.astro title: 'Starting a New Project' description: This guide shows you how to set up Snowpack from scratch in a Node.js project. Along the way learn key concepts of Snowpack and unbundled development. --- Welcome to Snowpack! This guide shows you how to set up Snowpack from scratch in a Node.js project. Along the way learn key concepts of Snowpack and unbundled development In this guide you'll learn - What makes Snowpack so fast? (hint: unbundled development!) - What are JavaScript ES Modules (ESM)? - Creating your first project - Starting Snowpack's development server - Building your first project - Customizing Snowpack with plugins > 💡 Tip: This guide walks you through creating the [Snowpack minimal app template](https://github.com/withastro/snowpack/tree/main/create-snowpack-app/) from scratch. Spin up a copy of the final [using the create-snowpack-app command line tool](https://github.com/withastro/snowpack/tree/main/create-snowpack-app/). Prerequisites: Snowpack is a command line tool installed from npm. This guide assumes a basic understanding of JavaScript, npm, and how to run commands in the terminal. Snowpack also requires a modern browser during development. Any semi-recent release of Firefox, Chrome, Safari, or Edge for example. ## Install Snowpack To get started, create an empty directory for your new Snowpack project. Create the new directory using your favorite GUI or by running the command line as shown here: ```bash mkdir my-first-snowpack cd my-first-snowpack ``` Snowpack is a package installed from npm. Create a `package.json` file in your project directory to manage your dependencies. Run this command in your project to create a simple, empty `package.json`: ```bash npm init ``` > 💡 Tip: In a hurry? Run `npm init --yes` to skip the prompts and generate a package.json with npm's default, recommended fields. Now install Snowpack to your `dev dependencies` with this command: ``` npm install --save-dev snowpack ``` > 💡 Tip: Snowpack can install globally via `npm install -g snowpack`. But, we recommend installing locally in every project via `--save-dev`/`--dev`. Run the Snowpack command-line tool locally via package.json "scripts", npm's `npx snowpack`, or via `yarn snowpack`. ## Snowpack's development server Adding a basic HTML file allows us to run Snowpack's development server, an instant development environment for unbundled development. The development server builds a file only when it's requested by the browser. That means that Snowpack can start up instantly (usually in **<50 ms**) and scale to infinitely large projects without slowing down. In contrast, it's common to see 30+ second development startup times when building large apps with a traditional bundler. Create an `index.html` in your project with the following contents: ```html Starter Snowpack App

Welcome to Snowpack!

``` Add the Snowpack development server to `package.json` under as the `start` script: ```diff "scripts": { + "start": "snowpack dev", "test": "echo \"Error: no test specified\" && exit 1" }, ``` Run the following on the command line to start the Snowpack development server ``` npm run start ``` If all went well, Snowpack automatically opens your site in a new browser!
Side by side of the terminal showing the dev server output. The dev server output displays the localhost address the project is running on. In a browser window you can see the running project on localhost, which is 'Welcome to Snowpack' on a white background.
Congratulations! You now have a Snowpack project up and running! Try changing the index.html and saving while the server is running, the site should refresh and show changes automatically. ## Using JavaScript Learn more about how Snowpack processes JavaScript by adding a simple "hello world" script. JavaScript's native ES Module (ESM) syntax is the magic behind Snowpack's unbundled development. There's a good chance that you're already familiar with ESM, and you just don't know it! ESM lets you define explicit imports & exports that browsers and build tools can better understand and optimize for. If you're familiar with the `import` and `export` keywords in JavaScript, then you already know ESM! Create a new JavaScript file called `hello-world.js` that exports a single `helloWorld` function: ```js // my-first-snowpack/hello-world.js export function helloWorld() { console.log('Hello World!'); } ``` Then create an `index.js` that imports your new module using ESM syntax: ```js // my-first-snowpack/index.js import {helloWorld} from './hello-world.js'; helloWorld(); ``` Snowpack scans for files referenced in `index.html`, so add your `index.js` to `index.html` at the bottom of the `` tag: ```diff

Welcome to Snowpack!

+ ``` Check your console on your Snowpack site. You should see "Hello World!" Try making a change to the module. Snowpack rebuilds that module without rebuilding the rest of your code. Snowpack builds **every file individually and caches it indefinitely.** Your development environment never builds a file more than once and your browser never downloads a file twice (until it changes). This is the real power of unbundled development, and the secret behind what makes Snowpack so fast.
Gif showing the code next to the project running in the browser. On save the console shows 'Hello World!'. On edit and save of the `hello-world.js` file to be 'Hello everyone!' instead, that instantly shows in the browser console.
## Using npm Packages Snowpack builds any npm package into ESM web modules. npm packages are mainly published using a module syntax (Common.js, or CJS) that can't run on the web without some build processing. Even if you write your application using browser-native ESM `import` and `export` statements that would all run directly in the browser, trying to import any one npm package forces you back into bundled development. **Snowpack takes a different approach:** instead of bundling your entire application for this one requirement, Snowpack processes your dependencies separately. Here's how it works: ``` node_modules/react/**/* -> http://localhost:3000/web_modules/react.js node_modules/react-dom/**/* -> http://localhost:3000/web_modules/react-dom.js ``` 1. Snowpack scans your website/application for all used npm packages. 2. Snowpack reads these installed dependencies from your `node_modules` directory. 3. Snowpack bundles all your dependencies separately into single JavaScript files. For example: `react` and `react-dom` convert to `react.js` and `react-dom.js`, respectively. 4. Each resulting file runs directly in the browser, and imported via ESM `import` statements. 5. Because your dependencies rarely change, Snowpack rarely needs to rebuild them. After Snowpack builds your dependencies, import any package and run it directly in the browser with zero extra bundling or tooling. This ability to import npm packages natively in the browser (without a bundler) is the foundation that all unbundled development and the rest of Snowpack builds on top of. Snowpack lets you import npm packages directly in the browser. Even if a package is using a legacy format, Snowpack up-converts the package to ESM before serving it to the browser. > 💡 Tip: when you start up your development server or run a new build, you may see a message that Snowpack is "installing dependencies." This means that Snowpack is converting your dependencies to run in the browser. Install the canvas-confetti package from npm and use it with the following command: ```bash npm install --save canvas-confetti ``` Now head to `index.js` and add this code: ```diff helloWorld(); +import confetti from 'canvas-confetti'; +confetti.create(document.getElementById('canvas'), { + resize: true, + useWorker: true, + })({ particleCount: 200, spread: 200 }); ``` > 💡 Tip: did you know, with Snowpack you can also add this code directly to your HTML if you prefer! You should now see a nifty confetti effect on your site.
Gif showing the code next to the project running in the browser. When the code snippet is added and saved, a confetti effect shows in the browser
> 💡 Tip: not all npm modules may work well in the browser. Modules dependent on Node.js built-in modules need a polyfill. You can enable this polyfill by setting Snowpack’s [`packageOptions.polyfillNode` configuration option](/reference/configuration#packageoptions.polyfillnode) to `true`. ## Adding CSS Snowpack natively supports many file types. CSS and CSS Modules for example. Add a simple CSS file to see how it works. Add the following css as a new `index.css` file: ```css body { font-family: sans-serif; } ``` Include it in your project by adding it to index.html in the `` ```diff + Starter Snowpack App ```
image showing the effects of the new CSS file: the font has changed from serif to sans-serif
## Build for production/deployment OK you've now built a very simple website and you want to launch it. It's time to use `snowpack build`. By default, `snowpack build` builds your site using the same unbundled approach as the `dev` command. Building is integrated with your development setup which guarantees a near-exact copy of the same code that you saw during development. Add the `snowpack build` command to package.json so it's easier to run on the command line: ```diff "scripts": { "start": "snowpack dev", + "build": "snowpack build", "test": "echo \"Error: no test specified\" && exit 1" }, ``` Now you can run this in your terminal: ```bash npm run build ``` You should see a new directory called `build` that contains a copy of your Snowpack project ready for deployment.
GIF terminal running Snowpack build, showing output, then clicking on the new `build` directory
## Next Steps You've just learned the major concepts of unbundled developments and built a tiny Snowpack project. But that's just the beginning with Snowpack. What's next? Our docs site has several great resources - [Bundling for production guide](/guides/optimize-and-bundle): how to connect a bundler like Webpack to optimize code for production deployments - [Plugins](/plugins): a list of plugins that allow you to integrate your favorite tools with Snowpack - [Templates/Examples](https://github.com/withastro/snowpack/tree/main/create-snowpack-app/cli): pre-built projects you can build on or just explore using many popular frameworks and tools - [Guides](/guides): Step by step deep dives on building with and for Snowpack. Includes frameworks like React and Svelte. If you have any questions, comments, or corrections, we'd love to hear from you in the Snowpack [discussion](https://github.com/withastro/snowpack/discussions) forum or our [Snowpack Discord community](https://discord.gg/rS8SnRk). ================================================ FILE: docs/tutorials/quick-start.md ================================================ --- layout: ../../layouts/content.astro title: Quick Start description: A very basic guide for developers who want to run Snowpack as quickly as possible. --- ### Install Snowpack ```bash # npm: npm install --save-dev snowpack # yarn: yarn add --dev snowpack # pnpm: pnpm add --save-dev snowpack ``` ### Run the Snowpack CLI ```bash npx snowpack [command] yarn run snowpack [command] pnpm run snowpack [command] ``` Throughout our documentation, we'll use `snowpack [command]` to document the CLI. To run your locally installed version of Snowpack, add the `npx`/`yarn run`/`pnpm run` prefix of the package manager that you used to install Snowpack. For long-term development, the best way to use Snowpack is with a package.json script. This reduces your own need to remember exact Snowpack commands/configuration, and lets you share some common scripts with the rest of your team (if applicable). ```js // Recommended: package.json scripts // npm run start (or: "yarn run ...", "pnpm run ...") "scripts": { "start": "snowpack dev", "build": "snowpack build" } ``` ### Serve your project locally ``` snowpack dev ``` This starts the local dev server for development. By default this serves your current working directory to the browser, and will look for an `index.html` file to start. You can customize which directories you want to serve via the ["mount"](/reference/configuration) configuration. ### Build your project ``` snowpack build ``` This builds your project into a static `build/` directory that you can deploy anywhere. You can customize your build via [configuration](/reference/configuration). ### See all commands & options ``` snowpack --help ``` The `--help` flag will display helpful output. ================================================ FILE: docs/tutorials/react.md ================================================ --- layout: ../../layouts/content-with-cover.astro title: 'Getting Started with React' description: 'Get started with this in-depth tutorial on how to build React applications and websites with Snowpack and developer tools like React Fast Refresh' date: 2020-12-01 tags: communityGuide cover: '/img/ReactGuide.jpg' img: '/img/ReactGuide.jpg' --- Snowpack is a great fit for [React](https://reactjs.org/) projects of any size. It's easy to get started and can scale to projects containing thousands of components and pages without any impact on development speed. Unlike traditional React application tooling, Snowpack saves you from getting bogged down with complex bundler setups and configuration files. In this guide, you'll go from an empty directory to a fully configured Snowpack project with support for React and several other useful developer tools. In the process, you'll learn: - How to set up your Snowpack development environment - Adding your first React component - Working with CSS, images and other web assets - Enabling [Fast Refresh](https://reactnative.dev/docs/fast-refresh) mode for React - Connecting your favorite tools Prerequisites: Snowpack is a command line tool installed from npm. This guide assumes a basic understanding of Node.js, npm, and how to run commands in the terminal. Knowledge of React is not required, Snowpack is a great way to learn React! > 💡 Tip: if you want to jump to the end to see a full featured React setup, the [Create Snowpack App React template](https://github.com/withastro/snowpack/tree/main/create-snowpack-app/app-template-react) comes with everything you'll learn in this guide plus other useful tools. ## Getting started The easiest way to start a new Snowpack project is with [Create Snowpack App](https://github.com/withastro/snowpack/tree/main/create-snowpack-app/cli), a tool to set up Snowpack in a new directory. `@snowpack/project-template-minimal` is a Create Snowpack App template for a simple, bare-bones Snowpack project setup that the rest of this guide builds on. To get started, open your terminal and head to a directory where you want to put your new project. Now run the following command in your terminal to create a new directory called `react-snowpack` with the minimal template automatically installed. ```bash npx create-snowpack-app react-snowpack --template @snowpack/app-template-minimal ``` You can now head to the new directory and start Snowpack with the following two commands: ```bash cd react-snowpack npm run start ``` You should see your new website up and running! > 💡 Tip: the `README.md` in your new project contains useful information about what each file does.
screenshot of project-template-minimal, which shows 'Hello world' in text on a white background.
Now that you have a basic project up and running, to install React, run the following command in your project directory: ```bash npm install react react-dom --save ``` > 💡 Tip: add the `--use-yarn` or `--use-pnpm` flag to use something other than npm ## Create your first React component React relies on a special templating language called JSX. If you're familiar with React then you already know JSX: it's React's templating language that allows you to write something like `` or `
` directly in your JavaScript code. Snowpack has built in support for JSX in files using the `.jsx` extension. That means that there's no plugins or configuration needed to write your first React component. Rename `index.js` file to `index.jsx` so that Snowpack knows to handle JSX in the file: ```bash mv index.js index.jsx ``` > 💡 Tip: you do not need to update your `index.html` script tag reference to point to `index.jsx`. Browsers don't speak JSX (or TypeScript, for that matter), so any compile-to-JS file formats compile to `.js` in the final browser build. This is good to keep in mind when you're referencing built files in HTML ` ```
screenshot of the project, which shows 'HELLO REACT' on a white background
You've just created your first React component in Snowpack! ## Customize your project layout Since you'll be adding a bunch of new files, you probably don't want them crowding up your top-level root directly. Snowpack is flexible enough to support whatever project layout that you prefer. In this guide, you'll learn how to use a popular project pattern from the React community. ``` 📁 src : your React components and their assets (CSS, images) ↳ index.jsx 📁 public : global assets like images, fonts, icons, and global CSS ↳ index.css ↳ index.html ``` Use your favorite visual editor to rearrange and rename, or run these commands in the terminal: ```bash mkdir src mkdir public mv index.jsx src/index.jsx mv index.html public/index.html mv index.css public/index.css ``` This means if you are running Snowpack right now, the site is now broken as the files are all in different places. Lets add a "mount" configuration to update your site to your new project layout. The `mount` configuration changes where Snowpack looks for and builds files. Every Snowpack project comes with a `snowpack.config.mjs` file for any configuration that you might need. Right now, you should see a configuration file with empty options. Add this to the empty `mount` object: ```diff export default { mount: { - /* ... */ + // directory name: 'build directory' + public: '/', + src: '/dist', }, }; ``` The original file configuration had Snowpack building the directory structure the same as the directories in the project, including root. Now the config builds only src and public. Src to the dist folder and public to root. `mount` is part of the [Snowpack Configuration API](/reference/configuration). It allows you to customize the file structure of your project. The key is the name of the directory and the value is where you'd like them in the final build. With this new configuration, Snowpack builds files in `public` like `public/index.css` directory into `index.css`. It builds files in `src` like `src/index.js` into `/dist/index.js`, so you'll need to change that path in your `index.html`: ```diff

Welcome to Snowpack!

- + ``` You'll need to restart Snowpack for configuration file changes. When you start up again, if it worked, it should look the same. Create a new file at `src/App.jsx` and paste the following code into this new file to create an `App` component: ```jsx import React, {useState, useEffect} from 'react'; function App() { // Create the count state. const [count, setCount] = useState(0); // Update the count (+1 every second). useEffect(() => { const timer = setTimeout(() => setCount(count + 1), 1000); return () => clearTimeout(timer); }, [count, setCount]); // Return the App component. return (

Page has been open for {count} seconds.

); } export default App; ``` Now include it in `index.jsx` ```diff import React from 'react'; import ReactDOM from 'react-dom'; - ReactDOM.render(
"HELLO WORLD"
, document.getElementById('root')); + import App from './App.jsx'; + ReactDOM.render( + + + , + document.getElementById('root'), + ); ``` > 💡 Tip: [Strict Mode](https://reactjs.org/docs/strict-mode.html) is a tool for highlighting potential problems in React code. You shouldn't need to restart Snowpack to see this, it should look like this:
screenshot of the project with text that says 'Page has been open for' and the number of seconds then 'seconds'
## Styling your project When you add assets like images or CSS, Snowpack includes them in your final build. If you already know React, this process should look pretty familiar. > 💡 Tip: as you're doing this, you should not need to reload the page or restart Snowpack. Snowpack automatically updates the project in the browser as you edit code. Add this file [`logo.svg`](https://github.com/withastro/snowpack/blob/main/create-snowpack-app/app-template-react/src/logo.svg) to your `src` directory. Now you can import it into your `App.jsx` and use it in an `img` tag to display it. ```diff import React, { useState, useEffect } from 'react'; + import logo from './logo.svg'; function App() { // Create the count state. const [count, setCount] = useState(0); // Create the counter (+1 every second). useEffect(() => { const timer = setTimeout(() => setCount(count + 1), 1000); return () => clearTimeout(timer); }, [count, setCount]); // Return the App component. return (
+ logo

```

the React logo (a blue atom) is now at the top of the page
The project already has index.css for global styles. For CSS that's only for a specific component, a common design pattern is to add it in a CSS file with the same base name as the component. The style file for `App.jsx` would be `App.css` with this pattern. > 💡 Tip: Snowpack has built-in support for [CSS Modules](/reference/supported-files) and if you'd like to use Sass there is an official [Sass Plugin](/guides/sass/). Create `src/App.css` and add this CSS: ```css .App { text-align: center; } .App p { margin: 0.4rem; } .App-logo { height: 40vmin; } @media (prefers-reduced-motion: no-preference) { .App-logo { animation: App-logo-spin infinite 20s linear; } } .App-header { background-color: #282c34; min-height: 100vh; display: flex; flex-direction: column; align-items: center; justify-content: center; font-size: calc(10px + 2vmin); color: white; } @keyframes App-logo-spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } } ``` To use this CSS, head to `App.jsx` and import it ```diff import logo from './logo.svg'; + import './App.css'; ```
The page now has centered items, a grey background, styled fonts, and the React logo has an animation that rotates it.
## Making Snowpack Even Faster with Fast Refresh [React Fast Refresh](https://reactnative.dev/docs/fast-refresh)? What's that? It's a Snowpack enhancement that lets you push individual file changes to update the browser without refreshing the page or clearing component state. React projects are often interactive and include state. For example, this project you're building has a state that is the amount of time on the page. When developing with state it's useful not to lose it while you edit code. React Fast Refresh shows you updates without refreshing the entire page. Showing you how to add this is also a good intro to Snowpack plugins. Snowpack starts with a minimal setup with the perspective that you can add what you need through the plugin system. Start by enabling [Hot Module Replacement](/concepts/hot-module-replacement) in your project. HMR is the system that lets Snowpack push updates to the browser without a full page refresh, a requirement for Fast Refresh. You can enable HMR for React by adding a small snippet of code to your `src/index.jsx` file. ```diff ReactDOM.render( , document.getElementById('root'), ); + // Hot Module Replacement (HMR) - Remove this snippet to remove HMR. + // Learn more: https://www.snowpack.dev/concepts/hot-module-replacement + if (import.meta.hot) { + import.meta.hot.accept(); + } ``` Now when you change `App.jsx` the page updates to show your changes without a full refresh.
GIF showing code side by side with the app. A change in made to App.jsx and it shows immediately when the file is changed. The counter keeps counting uninterrupted.
HMR can save you time on its own, but you may notice in the example above that the counter on the page still resets to 0. This can slow down your development, especially when you're trying to debug a specific component state problem. Lets enable Fast Refresh to preserve component state across updates. To enable Fast Refresh, you'll need to install the `@snowpack/plugin-react-refresh` package. This package is a Snowpack plugin, which you can use to enhance or customize Snowpack with all sorts of new behaviors. To start, install the package in your project: ```bash npm install @snowpack/plugin-react-refresh --save-dev ``` Once installed, you'll need to add the plugin to your Snowpack configuration file so that Snowpack knows to use it: ```diff module.exports = { mount: { public: '/', src: '/dist', }, - plugins: [] + plugins: ['@snowpack/plugin-react-refresh'], }; ``` Restart Snowpack to apply the new plugin, and then try changing the `App.jsx` component again. If Fast Refresh is working properly, the counter keeps its value across changes, without resetting to zero.
GIF showing code side by side with the app. A change in made to App.jsx and it shows immediately when the file is changed. The counter keeps counting uninterrupted.
## Going further Great job! You're now ready to build the React project of your dreams with Snowpack. Want to tweet your accomplishment to the world? Click the button below: At this point you have the basics and have a great starter for any React project. But if you compare with the official [Snowpack React template](https://github.com/withastro/snowpack/tree/main/create-snowpack-app/app-template-react) you'll notice it has some other developer tools you might find useful: - [Prettier](https://prettier.io/) — a popular code formatter - [Tests](/guides/testing) — Snowpack supports any popular JavaScript testing framework - [`@snowpack/plugin-dotenv`](https://github.com/withastro/snowpack/tree/main/plugins/plugin-dotenv) — Use `dotenv` in your Snowpack. This is useful for environment specific variables If you'd like to use Typescript with Snowpack and React, check out the [Snowpack React Typescript](https://github.com/withastro/snowpack/tree/main/create-snowpack-app/app-template-react-typescript) starter. If you have any questions, comments, or corrections, we'd love to hear from you in the Snowpack [discussion](https://github.com/withastro/snowpack/discussions) forum or our [Snowpack Discord community](https://discord.gg/rS8SnRk). ================================================ FILE: docs/tutorials/svelte.md ================================================ --- layout: ../../layouts/content-with-cover.astro title: 'Getting Started with Svelte' description: 'Get started with this in-depth tutorial on how to build Svelte applications and websites with Snowpack' date: 2020-12-01 sidebarTitle: Svelte tags: communityGuide cover: '/img/SvelteGuide.jpg' img: '/img/SvelteGuide.jpg' --- Snowpack is a great fit for [Svelte](https://svelte.dev/) projects of any size. It's easy to get started and can scale to projects containing thousands of components and pages without any impact on development speed. Unlike traditional Svelte application tooling, Snowpack saves you from getting bogged down with complex bundler setups and configuration files. > Snowpack is … astonishingly fast, and has a beautiful development experience (hot module reloading, error overlays and so on), and we've been working closely with the Snowpack team on features like SSR[Server-side rendering]. The hot module reloading is particularly revelatory. - [Rich Harris, creator of Svelte](https://svelte.dev/blog/whats-the-deal-with-sveltekit) This guide is a step by step from an empty directory to a fully configured Snowpack project, in the process teaching: - How to set up your Snowpack development environment - Adding your first Svelte component - Importing images and other web assets - Enabling Hot Module Replacement (HMR) - Connecting your favorite tools Prerequisites: Snowpack is a command-line tool installed from npm. This guide assumes a basic understanding of Node.js, npm, and how to run commands in the terminal. Knowledge of Svelte is not required: Snowpack is an excellent way to learn Svelte! > 💡 Tip: a [Svelte/Snowpack](https://github.com/withastro/snowpack/tree/main/create-snowpack-app/app-template-svelte) working example is available in our Create Snowpack App templates. ## Getting started The easiest way to start a new Snowpack project is with [Create Snowpack App](https://github.com/withastro/snowpack/tree/main/create-snowpack-app/cli), a tool for creating a new project based on our example templates. `@snowpack/app-template-minimal` is a Create Snowpack App template for a simple, bare-bones Snowpack project setup that the rest of this guide builds on. Run the following command in your terminal to create a new directory called `svelte-snowpack` with the minimal template installed: ```bash npx create-snowpack-app svelte-snowpack --template @snowpack/app-template-minimal ``` Head to the new `svelte-snowpack` directory and start Snowpack with the following two commands: ```bash cd svelte-snowpack npm run start ``` You should see your new website up and running!
screenshot of project-template-minimal, which shows 'Hello world' in text on a white background.
Now that you have a basic project up and running! The next step is to install Svelte. Run the following command in your project directory: ```bash npm install svelte --save ``` > 💡 Tip: add the `--use-yarn` or `--use-pnpm` flag to use something other than npm ```bash npm install @snowpack/plugin-svelte --save-dev ``` Snowpack [plugins](/plugins) are a way to extend Snowpack's capabilities without having to do custom configuration yourself. Install the `@snowpack/plugin-svelte` plugin so that Snowpack knows how built `.svelte` files into JavaScript and CSS files that run in the browser: Once installed, you'll need to add the plugin to your Snowpack configuration file (`snowpack.config.mjs`) so that Snowpack knows to use it: ```diff // snowpack.config.mjs export default { mount: { /* ... */ }, plugins: [ - /* ... */ + '@snowpack/plugin-svelte', ], }; ``` Restart your Snowpack dev server to run it with the new configuration. Exit the process (ctrl + c in most Windows/Linux/macOS) and start it again with `npm run start`. > 💡 Tip: Restart the Snowpack development server when you make configuration changes (changes to the `snowpack.config.mjs`). Snowpack will recognize the new dependency (Svelte, or "svelte/internal") and print the following output as installs your dependencies for the frontend: ```bash [snowpack] installing dependencies... [snowpack] ✔ install complete! [0.45s] [snowpack] ⦿ web_modules/ size gzip brotli ├─ svelte-hmr/runtime/hot-api-esm.js 22.17 KB 7.42 KB 6.3 KB ├─ svelte-hmr/runtime/proxy-adapter-dom.js 5.17 KB 1.65 KB 1.38 KB └─ svelte/internal.js 52.78 KB 13.24 KB 11.45 KB ``` ## Create your first Svelte component You now have your Snowpack environment set up to build `.svelte` files for the browser. Now it's time to create your first Svelte component file! Create a file named `App.svelte` in your project directory with the following code: ```html ``` Now you can use the new `App.svelte` file in your `index.js`: ```diff // index.js /* Add JavaScript code here! */ -console.log('Hello World! You did it! Welcome to Snowpack :D'); +import App from "./App.svelte"; +let app = new App({ + target: document.body, +}); +export default app; ``` The page should now say "Learn Svelte". Congratulations! you now have your first Svelte component!
code and site side by side, site is a 'Learn Svelte' link on a white background. When the text is edit to add 'Hello world' and the file saves, the changes show up in the site immediately.
## Customize your project layout Snowpack is flexible enough to support whatever project layout that you prefer. In this guide, you'll learn how to use a popular project pattern from the Svelte community. ``` 📁 src : your Svelte components and their assets (CSS, images) ↳ index.js ↳ App.svelte 📁 public : global assets like images, fonts, icons, and global CSS ↳ index.css ↳ index.html ``` Use your favorite visual editor to rearrange and rename, or run these commands in the terminal: ```bash mkdir src mkdir public mv index.js src/index.js mv App.svelte src/App.svelte mv index.html public/index.html mv index.css public/index.css ``` This means if you are running Snowpack right now, the site is now broken as the files are all in different places. Lets add a "mount" configuration to update your site to your new project layout. The `mount` configuration changes where Snowpack scan for and builds files. Head back to the `snowpack.config.mjs` file you edited when you added `@snowpack/plugin-svelte`. Add this to the empty `mount` object: ```diff // snowpack.config.mjs export default { mount: { - /* ... */ + // directory name: 'build directory' + public: '/', + src: '/dist', }, }; ``` Graphic shows the original and new folder structures side by side. Arrows indicate that the files are built to where the arrow points. The Original side shows a folder labeled ./ entire directory with an arrow pointing to a folder labeled  mysite.com/*. The New side shows a folder labeled ./src/* with an arrow pointing to a folder labeled mysite.com/_dist/*. Then a second folder labeled ./public/* with an arrow pointing to a folder labeled mysite.com/* `mount` is part of the [Snowpack Configuration API](/reference/configuration). It allows you to customize the file structure of your project. The key is the name of the directory and the value is where you'd like them in the final build. With this new configuration, Snowpack builds files in the `public` directory - like `public/index.css` - into `index.css`. Likewise, it builds files in `src` like `src/index.js` into `/dist/index.js`, so change that path in your `index.html`: ```diff

Welcome to Snowpack!

- + ``` You'll need to restart Snowpack (stop the process in terminal and then run `npm start` again) for configuration file changes. It should look exactly as it did before, but now using your brand new project folder layout ## Adding an animated Svelte Logo In Svelte you can add CSS directly to your component. This step demonstrates this capability by adding an animated logo. [Download `logo.svg`](https://github.com/withastro/snowpack/blob/main/create-snowpack-app/app-template-svelte/public/logo.svg) to your `public` directory. Now you can add it to your `App.svelte` ```diff
+ Learn Svelte ```
Side by side of code and site. The site now has a very large Svelte logo. The code shows the src/App.svelte file
With Svelte, CSS can go directly in your `.svelte` component. Add this code to the top of `App.svelte` between the ` ```
code and site side by side, when the css is added to the Svelte component, the background becomes a beige, the logo shrinks down, and the logo has a pulsing animation
## Adding a counter to your Svelte component Snowpack is one of the only Svelte dev environments to support Fast Refresh by default. With Fast Refresh, as you make changes to `.svelte` files, Snowpack pushes live updates to the browser without losing your place or resetting component state. To see this for yourself, go ahead and add a simple timer to your App.svelte component. Svelte components include component specific scripts in a ` ``` Then lower down in your component's body, add this code that displays the results of the timer. ```diff
+

Page has been open for {count} seconds.

Learn Svelte
``` Change some code on the page (like the "Learn Svelte" button). You'll see the timer does not reset.
Showing code and site side by side, when the word 'Hello' is added to the .svelte page and the code is saved, the change shows up in the browser without the timer resetting (it keeps counting)
What about other, non-Svelte files like `src/index.js`? To re-render your Svelte application when other files change, add this code snippet to the bottom: ```diff export default app; +// Hot Module Replacement (HMR) - Remove this snippet to remove HMR. +// Learn more: https://www.snowpack.dev/concepts/hot-module-replacement +if (import.meta.hot) { + import.meta.hot.accept(); + import.meta.hot.dispose(() => { + app.$destroy(); + }); +} ``` ## Going further Great job! You're now ready to build the Svelte project of your dreams with Snowpack. Want to tweet your accomplishment to the world? Click the button below: At this point you have the basics and have a great starter for any Svelte project. The official [Snowpack Svelte](https://github.com/withastro/snowpack/tree/main/create-snowpack-app/app-template-svelte) example has a few other tools you might find useful: - [Prettier](https://prettier.io/) — a popular code formatter - [Tests](/guides/testing) — Snowpack supports any popular JavaScript testing framework - [`@snowpack/plugin-dotenv`](https://github.com/withastro/snowpack/tree/main/plugins/plugin-dotenv) — Use `dotenv` in your Snowpack. This is useful for environment specific variables We also recommend the official [Svelte](https://svelte.dev/tutorial/basics) tutorial, which teaches more about how Svelte works and how to build Svelte components. If you'd like to use Typescript with Snowpack and Svelte, check out the [Snowpack Svelte Typescript](https://github.com/withastro/snowpack/tree/main/create-snowpack-app/app-template-svelte-typescript) template. If you have any questions, comments, or corrections, we'd love to hear from you in the Snowpack [discussion](https://github.com/withastro/snowpack/discussions) forum or our [Snowpack Discord community](https://discord.gg/rS8SnRk). ================================================ FILE: docs/tutorials/vue.md ================================================ --- layout: ../../layouts/content-with-cover.astro title: 'Getting Started with Vue' description: 'Get started with this in-depth tutorial on how to build Vue applications and websites with Snowpack' date: 2020-12-01 sidebarTitle: Vue tags: communityGuide --- Snowpack is a great fit for [Vue](https://vuejs.org) projects of any size. It's easy to get started and can scale to projects containing thousands of components and pages without any impact on development speed. Unlike traditional Vue application tooling, Snowpack saves you from getting bogged down with complex bundler setups and configuration files. This guide is a step by step from an empty directory to a fully configured Snowpack project, in the process teaching: - How to set up your Snowpack development environment - Adding your first Vue component - Importing images and other web assets - Enabling Hot Module Replacement (HMR) - Connecting your favorite tools Prerequisites: Snowpack is a command-line tool installed from npm. This guide assumes a basic understanding of Node.js, npm, and how to run commands in the terminal. Knowledge of Vue is not required; Snowpack is an excellent way to learn Vue! > 💡 Tip: a [Vue/Snowpack](https://github.com/withastro/snowpack/tree/main/create-snowpack-app/app-template-vue) working example is available in our Create Snowpack App templates. ## Getting started The easiest way to start a new Snowpack project is with [Create Snowpack App](https://github.com/withastro/snowpack/tree/main/create-snowpack-app/cli), a tool for creating a new project based on our example templates. `@snowpack/app-template-minimal` is a Create Snowpack App template for a simple, bare-bones Snowpack project setup that the rest of this guide builds on. Run the following command in your terminal to create a new directory called `vue-snowpack` with the minimal template installed: ```bash npx create-snowpack-app vue-snowpack --template @snowpack/app-template-minimal ``` Head to the new `vue-snowpack` directory and start Snowpack with the following two commands: ```bash cd vue-snowpack npm run start ``` You should see your new website up and running!
screenshot of project-template-minimal, which shows 'Hello world' in text on a white background.
Now that you have a basic project up and running, the next step is to install Vue. Run the following command in your project directory: ```bash npm install vue@3.0.11 --save ``` > 💡 Tip: add the `--use-yarn` or `--use-pnpm` flag to use something other than npm ```bash npm install @snowpack/plugin-vue --save-dev ``` Snowpack [plugins](/plugins) are a way to extend Snowpack's capabilities without having to do custom configuration yourself. Install the `@snowpack/plugin-vue` plugin so that Snowpack knows how built `.vue` files into JavaScript and CSS files that run in the browser: Once installed, you'll need to add the plugin to your Snowpack configuration file (`snowpack.config.mjs`) so that Snowpack knows to use it: ```diff // snowpack.config.mjs export default { mount: { /* ... */ }, plugins: [ + '@snowpack/plugin-vue', ], }; ``` Restart your Snowpack dev server to run it with the new configuration. Exit the process (ctrl + c in most Windows/Linux/macOS) and start it again with `npm run start`. > 💡 Tip: Restart the Snowpack development server when you make configuration changes (changes to the `snowpack.config.mjs`). Snowpack will recognize the new dependency (Vue, or "vue/internal") and print the following output as installs your dependencies for the frontend: ```bash [snowpack] installing dependencies... [snowpack] ✔ install complete! [0.45s] [snowpack] + vue@3.0.11 └── @vue/runtime-dom@3.0.11 └── @vue/runtime-core@3.0.11 └── @vue/reactivity@3.0.11 └── @vue/shared@3.0.11 ``` ## Create your first Vue component You now have your Snowpack environment set up to build `.vue` files for the browser. Now it's time to create your first Vue component file! Create a file named `App.vue` in your project directory with the following code: ```html ``` Add an ID of `#root` to the `body` tag in your `index.html` ```diff // index.html - +

Welcome to Snowpack!

``` Now you can use the new `App.vue` file in your `index.js`: ```diff // index.js - console.log('Hello World! You did it! Welcome to Snowpack :D'); + import { createApp } from 'vue'; + import App from './App.vue'; + createApp(App).mount('#root'); ``` The page should now say "Welcome to my Vue app!". Congratulations! You now have your first Vue component! ## Customize your project layout Snowpack is flexible enough to support whatever project layout that you prefer. In this guide, you'll learn how to use a popular project pattern from the Vue community. ``` ├── src/ <- your Vue components and their assets (CSS, images) │ ├── index.js │ └── App.vue └── public/ <- global assets like images, fonts, icons, and global CSS ├── index.css └── index.html ``` Use your favorite visual editor to rearrange and rename, or run these commands in the terminal: ```bash mkdir src mkdir public mv index.js src/index.js mv App.vue src/App.vue mv index.html public/index.html mv index.css public/index.css ``` This means if you are running Snowpack right now, the site is now broken as the files are all in different places. Lets add a "mount" configuration to update your site to your new project layout. The `mount` configuration changes where Snowpack scan for and builds files. Head back to the `snowpack.config.mjs` file you edited when you added `@snowpack/plugin-vue`. Add this to the empty `mount` object: ```diff // snowpack.config.mjs export default { mount: { - /* ... */ + public: '/', + src: '/dist', }, }; ``` Graphic shows the original and new folder structures side by side. Arrows indicate that the files are built to where the arrow points. The Original side shows a folder labeled ./ entire directory with an arrow pointing to a folder labeled  mysite.com/*. The New side shows a folder labeled ./src/* with an arrow pointing to a folder labeled mysite.com/_dist/*. Then a second folder labeled ./public/* with an arrow pointing to a folder labeled mysite.com/* `mount` is part of the [Snowpack Configuration API](/reference/configuration). It allows you to customize the file structure of your project. The key is the name of the directory and the value is where you'd like them in the final build. With this new configuration, Snowpack builds files in the `public` directory (e.g. `public/index.css -> [build]/index.css`). Likewise, it builds files in `src` (e.g. `src/index.js -> [build]/dist/index.js`, so change that path in your `index.html`: ```diff

Welcome to Snowpack!

- + ``` You'll need to restart Snowpack (stop the process in terminal and then run `npm start` again) for configuration file changes. It should look exactly as it did before, but now using your brand new project folder layout ## Adding an animated Vue Logo In Vue you can add CSS directly to your component. This step demonstrates this capability by adding an animated logo. [Download `logo.svg`](https://github.com/withastro/snowpack/blob/main/create-snowpack-app/app-template-vue/public/logo.svg) to your `public` directory. Now you can add it to your `App.vue` ```diff
+ Learn Vue ``` With Vue, CSS can go directly in your `.vue` component. Add this code to the top of `App.vue` between the ` ``` ## Adding a counter to your Vue component Snowpack is one of the only Vue dev environments to support Fast Refresh by default. With Fast Refresh, as you make changes to `.vue` files, Snowpack pushes live updates to the browser without losing your place or resetting component state. To see this for yourself, go ahead and add a simple timer to your `App.vue` component. Vue components include component specific scripts in a ` ``` Then lower down in your component's body, add this code that displays the results of the timer. ```diff
+

Page has been open for {count} seconds.

Learn Vue
``` Change some code on the page (like the "Learn Vue" button). You'll see the timer does not reset. What about other, non-Vue files like `src/index.js`? To re-render your Vue application when other files change, add this code snippet to the bottom: ```diff // src/index.js export default app; + // Hot Module Replacement (HMR) - Remove this snippet to remove HMR. + // Learn more: https://www.snowpack.dev/concepts/hot-module-replacement + if (import.meta.hot) { + import.meta.hot.accept(); + import.meta.hot.dispose(() => { + app.$destroy(); + }); + } ``` ## Going further Great job! You're now ready to build the Vue project of your dreams with Snowpack. Want to tweet your accomplishment to the world? Click the button below: At this point you have the basics and have a great starter for any Vue project. The official [Snowpack Vue](https://github.com/withastro/snowpack/tree/main/create-snowpack-app/app-template-vue) example has a few other tools you might find useful: - [Prettier](https://prettier.io/) — a popular code formatter - [Tests](/guides/testing) — Snowpack supports any popular JavaScript testing framework - [`@snowpack/plugin-dotenv`](https://github.com/withastro/snowpack/tree/main/plugins/plugin-dotenv) — Use `dotenv` in your Snowpack. This is useful for environment specific variables If you have any questions, comments, or corrections, we'd love to hear from you in the Snowpack [discussion](https://github.com/withastro/snowpack/discussions) forum or our [Snowpack Discord community](https://discord.gg/rS8SnRk). ================================================ FILE: esinstall/.gitignore ================================================ node_modules lib ================================================ FILE: esinstall/CHANGELOG.md ================================================ # esinstall ## 1.1.7 ### Patch Changes - 9b1472f6: Lock slash package version more aggressively ## 1.1.6 ### Patch Changes - fb7eaaa4: Allow `external` CommonJS modules to be imported properly in Node (#3473) - 1dac52b3: [ci] yarn format - afc6232f: Allow .astro files to be installed as JavaScript (#3406) ## 1.1.5 ### Patch Changes - c659b7c3: [ci] yarn format - b375b8a3: Fix `external` behavior for local package source and SSR. (#3399) - 068b7d75: Set `preventAssignment` option of rollup-plugin-replace to true (#3222) - d9956f73: add explicit "mode" config (#3135) ## 1.1.4 ### Patch Changes - 9eb50368: update deps (#2928) - d542762a: Add execa dependency for esinstall (#3075) ## 1.1.3 ### Patch Changes - 332d69ed: Adding property expr to solve #2945 (#3063) ## 1.1.2 ### Patch Changes - 56c5a231: fix typo in changelog - 836338b5: add react-transition-group to bad cjs scanner list - 22dd802e: no longer need tslib workaround - 74c4661e: add "events" to bad cjs scanning packages list - 40f02cf4: reorder changelog entries - fc6c1417: Improve CSS error message (#2973) ## 1.1.1 ### Patch Changes - e50cb745: fix better handling for unscannable cjs packages - 1232e252: add back cjs-cjs compat from an earlier pr (#2934) ## 1.1.0 ### Minor Changes - bea1c56c: Simplify. cleanup, enhance snowpack internals (#2707) ### Patch Changes - a800bf3d: finalize picomatch support (#2912) - b2738967: improve cjs<>esm conversion of named exports (#2859) - c3ecc7da: [ci] yarn format - 92057561: Fix: warn on missing export (#2826) - 6f514a0d: [ci] yarn format - 8280627d: fix issue with types package (#2768) - bb1ca50a: url not needed in esinstall - 19cdf5ca: ignore data imports from build or scan - 435692d0: make stats optional, speed up perf (#2708) - b82c472e: [ci] yarn format - 81d9ff88: fix unnecessary filtering of absolute aliases (#2424) - c0501f72: [ci] yarn format - d3b6f769: Support packages that use export maps but have no main (#2659) - 520112b6: Update the changelog ## 1.0.5 ### Patch Changes - ec3c29fd: Pin rollup version to fix tree shaking bugs (#2572) - 4ffc1c10: add support for internal export map imports (#2507) ## 1.0.4 ### Patch Changes - 150cdf30: Fix explodeExportMap expanding dirs (#2493) - 977621dc: properly scan named imports from events polyfill package ## 1.0.3 ### Patch Changes - 6b105339: add esinstall missing import hint (#2299) - 15a27ac9: support additional main fields in sub-dependencies (#2298) ## 1.0.2 ### Patch Changes - 15d77ea3: cleanup _For older releases, check our curated [release update thread](https://github.com/withastro/snowpack/discussions/1183) or the raw [commit history](https://github.com/withastro/snowpack/commits/main/esinstall)._ ================================================ FILE: esinstall/LICENSE ================================================ MIT License Copyright (c) 2019 Fred K. Schott Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: esinstall/README.md ================================================ # esinstall - Convert a set of imports from your `node_modules/` directory into a fresh, new 100% [ESM](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import) install directory. - JavaScript packages are converted to ESM regardless of how they were originally authored. - Import your new ESM dependencies in any ESM-only environment (websites, Deno, Node.js v14). ``` npm install esinstall ``` ```js import {install} from 'esinstall'; await install(['preact', 'preact/hooks'], { /*options*/ }); // Result: Creates `preact.js` and `preact/hooks.js` inside a `web_modules/` directory in your current directory. ``` ⚡️ Powering [Snowpack](https://www.snowpack.dev) and the next generation of JavaScript tooling. ## Status The core install logic of this library is considered well-tested and production-ready (1+ years of active use & development!). The JS interface is new, however, and may contain smaller bugs. We'll be working to stabilize the API over the next month, with a `1.0.0` release planned for October. ## Background Before [Snowpack](https://www.snowpack.dev/) was a frontend build tool, it was a CJS->ESM package converter. `snowpack install` would read your package.json "dependencies" and re-install every frontend package from your "node_modules/" directory to a new "web_modules/" directory. "web_modules/" was guarenteed to be 100% ESM regardless of how each package was originally written. This dramatically simplified the minimum tooling required to build a website by removing a whole class of problem for the frontend basically summarized as "oh no this package was written for Node.js, it will never run in the browser, what do we do???" Snowpack is now a fully-featured frontend build tool, but it's still built entirely on that original foundation. That foundation is now **esinstall**, a general-purpose JavaScript interface for creating ESM single-file versions of locally installed npm packages. Our hope is that now others can build on top of this too. And while today we're focused on the web use-case, [we're seeing a growing need for a CJS->ESM story for Node.js](https://changelog.com/jsparty/137) as well. ## How it Works To simplify things a ton, here's what **esinstall** does internally when you run `install()`: 1. Resolves the given package specifiers to exact file paths in your "node_modules/" directory. 2. Runs Rollup with those file paths as entrypoints, essentially "bundling" your dependency tree into as few JS files as possible. 3. Writes the result to a new, fully ESM output directory. If you check out the code, you'll see it's not as easy as it sounds. But at a high level, that's what **esinstall** is all about. ## Features ```js import {install, printStats} from 'esinstall'; // Feature: Handle CJS packages with ease, converting everything to ESM! await install(['react', 'react-dom', 'react-redux', 'react-router']); // Feature: Handle CSS! await install(['bootstrap/dist/css/bootstrap.min.css']); // Feature: Handle Non-standard packages! await install(['some-svelte-component'], {rollup: {plugins: [require('rollup-plugin-svelte')()]}}); // Feature: Print detailed install stats to the console, including installed file sizes. const {success, stats} = install([...]); if (success) { printStats(stats); } // Feature: Tree-shaking! Get a smaller final build by providing more detailed install targets. await install( [{specifier: 'preact/hooks', all: false, default: false, namespace: false, named: ['useState', 'useEffect']}], {treeshake: true} ); ``` ## API Still TODO: Adding more detailed descriptions about each `InstallOptions` option. ```ts import {Plugin as RollupPlugin} from 'rollup'; import { DependencyStatsOutput, EnvVarReplacements, ImportMap, InstallTarget, LoggerLevel, } from './types'; interface InstallOptions { cwd: string; alias: Record; lockfile?: ImportMap; logger: AbstractLogger; verbose?: boolean; dest: string; env: EnvVarReplacements; treeshake?: boolean; polyfillNode: boolean; sourceMap?: boolean | 'inline'; externalPackage: string[]; externalPackageEsm: string[]; packageLookupFields: string[]; packageExportLookupFields: string[]; namedExports: string[]; rollup: { context?: string; plugins?: RollupPlugin[]; dedupe?: string[]; }; } declare type InstallResult = | { success: false; importMap: null; stats: null; } | { success: true; importMap: ImportMap; stats: DependencyStatsOutput; }; export declare function printStats(dependencyStats: DependencyStatsOutput): string; export declare function install( _installTargets: (InstallTarget | string)[], _options?: Partial, ): Promise; ``` ## Special Thanks A huge thanks to all the contributors of Snowpack (and now **esinstall**) over the years. This wouldn't have been possible without you! Also, it can't be stressed enough: this tool would never have existed without Rollup. If you can, consider donating to their team: https://opencollective.com/rollup ================================================ FILE: esinstall/index.esm.mjs ================================================ import Pkg from './lib/index.js'; export const printStats = Pkg.printStats; export const install = Pkg.install; ================================================ FILE: esinstall/package.json ================================================ { "name": "esinstall", "version": "1.1.7", "description": "Convert packages to ESM.", "license": "MIT", "keywords": [ "install", "web", "dependencies", "npm", "esm", "common.js", "rollup", "esbuild", "cjs" ], "homepage": "https://github.com/withastro/snowpack/tree/main/esinstall#readme", "repository": { "type": "git", "url": "https://github.com/withastro/snowpack.git", "directory": "esinstall" }, "publishConfig": { "access": "public" }, "scripts": { "build": "tsc --noUnusedLocals false --noUnusedParameters false", "build:watch": "tsc --watch --noUnusedLocals false --noUnusedParameters false", "lint": "tsc --noEmit && package-check" }, "types": "lib/index.d.ts", "main": "lib/index.js", "exports": { "import": "./index.esm.mjs", "require": "./lib/index.js" }, "files": [ "lib", "index.esm.mjs" ], "dependencies": { "@rollup/plugin-commonjs": "^16.0.0", "@rollup/plugin-inject": "^4.0.2", "@rollup/plugin-json": "^4.0.0", "@rollup/plugin-node-resolve": "^10.0.0", "@rollup/plugin-replace": "^2.4.2", "builtin-modules": "^3.2.0", "cjs-module-lexer": "^1.2.1", "es-module-lexer": "^0.6.0", "execa": "^5.1.1", "is-valid-identifier": "^2.0.2", "kleur": "^4.1.1", "mkdirp": "^1.0.3", "picomatch": "^2.3.0", "resolve": "^1.20.0", "rimraf": "^3.0.0", "rollup": "~2.37.1", "rollup-plugin-polyfill-node": "^0.7.0", "slash": "~3.0.0", "validate-npm-package-name": "^3.0.0", "vm2": "^3.9.2" } } ================================================ FILE: esinstall/src/entrypoints.ts ================================================ import {readdirSync, existsSync, realpathSync, statSync} from 'fs'; import path from 'path'; import builtinModules from 'builtin-modules'; import validatePackageName from 'validate-npm-package-name'; import {ExportField, ExportMapEntry, PackageManifestWithExports, PackageManifest} from './types'; import {parsePackageImportSpecifier, resolveDependencyManifest} from './util'; import resolve from 'resolve'; import picomatch from 'picomatch'; export const MAIN_FIELDS = [ 'browser:module', 'module', 'browser', 'main:esnext', 'jsnext:main', 'main', ]; // Rarely, a package will ship a broken "browser" package.json entrypoint. // Ignore the "browser" entrypoint in those packages. const BROKEN_BROWSER_ENTRYPOINT = ['@sheerun/mutationobserver-shim']; const FILE_EXTENSION_REGEX = /\..+$/; function getMissingEntrypointHint( packageEntrypoint: string, normalizedMap: Record, ): string | undefined { const noExtensionEntrypoint = './' + packageEntrypoint.replace(FILE_EXTENSION_REGEX, ''); if (Reflect.get(normalizedMap, noExtensionEntrypoint)) { return `Did you mean "${noExtensionEntrypoint}"?`; } const jsExtensionEntrypoint = './' + packageEntrypoint.replace(FILE_EXTENSION_REGEX, '.js'); if (Reflect.get(normalizedMap, jsExtensionEntrypoint)) { return `Did you mean "${jsExtensionEntrypoint}"?`; } const cjsExtensionEntrypoint = './' + packageEntrypoint.replace(FILE_EXTENSION_REGEX, '.cjs'); if (Reflect.get(normalizedMap, cjsExtensionEntrypoint)) { return `Did you mean "${cjsExtensionEntrypoint}"?`; } const mjsExtensionEntrypoint = './' + packageEntrypoint.replace(FILE_EXTENSION_REGEX, '.cjs'); if (Reflect.get(normalizedMap, mjsExtensionEntrypoint)) { return `Did you mean "${mjsExtensionEntrypoint}"?`; } const directoryEntrypoint = './' + packageEntrypoint + '/index.js'; if (Reflect.get(normalizedMap, directoryEntrypoint)) { return `Did you mean "${directoryEntrypoint}"?`; } } type FindManifestEntryOptions = { packageLookupFields?: string[]; packageName?: string; }; /** * */ export function findManifestEntry( manifest: PackageManifest, entry?: string, {packageLookupFields = [], packageName}: FindManifestEntryOptions = {}, ): string | undefined { let foundEntrypoint: string | undefined; if (manifest.exports) { foundEntrypoint = typeof manifest.exports === 'string' ? manifest.exports : findExportMapEntry(manifest.exports['.'] || manifest.exports); if (typeof foundEntrypoint === 'string') { return foundEntrypoint; } } foundEntrypoint = [...packageLookupFields, ...MAIN_FIELDS].map((e) => manifest[e]).find(Boolean); if (foundEntrypoint && typeof foundEntrypoint === 'string') { return foundEntrypoint; } if (!(packageName && BROKEN_BROWSER_ENTRYPOINT.includes(packageName))) { // Some packages define "browser" as an object. We'll do our best to find the // right entrypoint in an entrypoint object, or fail otherwise. // See: https://github.com/defunctzombie/package-browser-field-spec let browserField = manifest.browser; if (typeof browserField === 'string') { return browserField; } else if (typeof browserField === 'object') { let browserEntrypoint = (entry && browserField[entry]) || browserField['./index.js'] || browserField['./index'] || browserField['index.js'] || browserField['index'] || browserField['./'] || browserField['.']; if (typeof browserEntrypoint === 'string') { return browserEntrypoint; } } } // If browser object is set but no relevant entrypoint is found, fall back to "main". return manifest.main; } /** * Given an ExportMapEntry find the entry point, resolving recursively. */ export function findExportMapEntry( exportMapEntry?: ExportMapEntry, conditions?: string[], ): string | undefined { // If this is a string or undefined we can skip checking for conditions if (typeof exportMapEntry === 'string' || typeof exportMapEntry === 'undefined') { return exportMapEntry; } let entry = exportMapEntry; if (conditions) { for (let condition of conditions) { if (entry[condition]) { entry = entry[condition]; } } } return ( findExportMapEntry(entry?.browser) || findExportMapEntry(entry?.import) || findExportMapEntry(entry?.default) || findExportMapEntry(entry?.require) || undefined ); } type ResolveEntrypointOptions = { cwd: string; packageLookupFields: string[]; }; /** * Resolve a "webDependencies" input value to the correct absolute file location. * Supports both npm package names, and file paths relative to the node_modules directory. * Follows logic similar to Node's resolution logic, but using a package.json's ESM "module" * field instead of the CJS "main" field. */ export function resolveEntrypoint( dep: string, {cwd, packageLookupFields}: ResolveEntrypointOptions, ): string { // We first need to check for an export map in the package.json. If one exists, resolve to it. const [packageName, packageEntrypoint] = parsePackageImportSpecifier(dep); const [packageManifestLoc, packageManifest] = resolveDependencyManifest(packageName, cwd); if (packageManifestLoc && packageManifest && typeof packageManifest.exports !== 'undefined') { const exportField = (packageManifest as PackageManifestWithExports).exports; // If this is a non-main entry point if (packageEntrypoint) { const normalizedMap = explodeExportMap(exportField, { cwd: path.dirname(packageManifestLoc), }); const mapValue = normalizedMap && Reflect.get(normalizedMap, './' + packageEntrypoint); if (typeof mapValue !== 'string') { let helpfulHint = normalizedMap && getMissingEntrypointHint(packageEntrypoint, normalizedMap); throw new Error( `Package "${packageName}" exists but package.json "exports" does not include entry for "./${packageEntrypoint}".` + (helpfulHint ? `\n${helpfulHint}` : ''), ); } return path.join(packageManifestLoc, '..', mapValue); } else { const exportMapEntry = exportField['.'] || exportField; const mapValue = findExportMapEntry(exportMapEntry); if (mapValue) { return path.join(packageManifestLoc, '..', mapValue); } } } // if, no export map and dep points directly to a file within a package, return that reference. if (builtinModules.indexOf(dep) === -1 && !validatePackageName(dep).validForNewPackages) { return realpathSync.native( resolve.sync(dep, {basedir: cwd, extensions: ['.js', '.mjs', '.ts', '.jsx', '.tsx']}), ); } // Otherwise, resolve directly to the dep specifier. Note that this supports both // "package-name" & "package-name/some/path" where "package-name/some/path/package.json" // exists at that lower path, that must be used to resolve. In that case, export // maps should not be supported. const [depManifestLoc, depManifest] = resolveDependencyManifest(dep, cwd); if (!depManifest) { try { const maybeLoc = realpathSync.native(resolve.sync(dep, {basedir: cwd})); return maybeLoc; } catch { // Oh well, was worth a try } } if (!depManifestLoc || !depManifest) { if (path.extname(dep) === '.css') { const parts = dep.split('/'); let npmName = parts.shift(); if (npmName && npmName.startsWith('@')) npmName += '/' + parts.shift(); throw new Error( `Module "${dep}" not found. If you‘re trying to import a CSS file from your project, try "./${dep}". If you‘re trying to import an NPM package, try running \`npm install ${npmName}\` and re-running Snowpack.`, ); } throw new Error( `Package "${dep}" not found. Have you installed it? ${depManifestLoc ? depManifestLoc : ''}`, ); } let foundEntrypoint = findManifestEntry(depManifest, dep, { packageName, packageLookupFields, }); // Sometimes packages don't give an entrypoint, assuming you'll fall back to "index.js". if (!foundEntrypoint) { for (let possibleEntrypoint of ['index.js', 'index.json']) { try { return realpathSync.native( resolve.sync(path.join(depManifestLoc || '', '..', possibleEntrypoint)), ); } catch {} } // Couldn't find any entrypoints so throwing throw new Error( `Unable to find any entrypoint for "${dep}". It could be a typo, or this package might not have a main entrypoint.`, ); } if (typeof foundEntrypoint !== 'string') { throw new Error(`"${dep}" has unexpected entrypoint: ${JSON.stringify(foundEntrypoint)}.`); } const finalPath = path.join(depManifestLoc || '', '..', foundEntrypoint); try { return realpathSync.native(resolve.sync(finalPath)); } catch { throw new Error(`We resolved "${dep}" to ${finalPath}, but the file does not exist on disk.`); } } const picoMatchGlobalOptions = Object.freeze({ capture: true, noglobstar: true, }); function* forEachExportEntry( exportField: ExportField, ): Generator<[string, ExportMapEntry], any, undefined> { const simpleExportMap = findExportMapEntry(exportField); // Handle case where export map is a string, or if there‘s only one file in the entire export map if (simpleExportMap) { yield ['.', simpleExportMap]; return undefined; } for (const [key, val] of Object.entries(exportField)) { // skip invalid entries if (!key.startsWith('.')) { continue; } yield [key, val]; } } function* forEachWildcardEntry( key: string, value: string, cwd: string, ): Generator<[string, string], any, undefined> { // Creates a regex from a pattern like ./src/extras/* let expr = picomatch.makeRe(value, picoMatchGlobalOptions); // The directory, ie ./src/extras let valueDirectoryName = path.dirname(value); let valueDirectoryFullPath = path.join(cwd, valueDirectoryName); if (existsSync(valueDirectoryFullPath)) { let filesInDirectory = readdirSync(valueDirectoryFullPath).filter( (filepath) => statSync(path.join(valueDirectoryFullPath, filepath)).isFile(), // ignore directories ); for (let filename of filesInDirectory) { // Create a relative path for this file to match against the regex // ex, ./src/extras/one.js let relativeFilePath = path.join(valueDirectoryName, filename); let match = expr.exec(relativeFilePath); if (match && match[1]) { let [matchingPath, matchGroup] = match; let normalizedKey = key.replace('*', matchGroup); // Normalized to posix paths, like ./src/extras/one.js let normalizedFilePath = '.' + path.posix.sep + matchingPath.split(path.sep).join(path.posix.sep); // Yield out a non-wildcard match, for ex. // ['./src/extras/one', './src/extras/one.js'] yield [normalizedKey, normalizedFilePath]; } } } } function* forEachExportEntryExploded( exportField: ExportField, cwd: string, ): Generator<[string, unknown], any, undefined> { for (const [key, val] of forEachExportEntry(exportField)) { // Deprecated but we still want to support this. // https://nodejs.org/api/packages.html#packages_subpath_folder_mappings if (key.endsWith('/')) { const keyValue = findExportMapEntry(val); if (typeof keyValue !== 'string') { continue; } // There isn't a clear use-case for this, so we are assuming it's not needed for now. if (key === './') { continue; } yield* forEachWildcardEntry(key + '*', keyValue + '*', cwd); continue; } // Wildcards https://nodejs.org/api/packages.html#packages_subpath_patterns if (key.includes('*')) { const keyValue = findExportMapEntry(val); if (typeof keyValue !== 'string') { continue; } yield* forEachWildcardEntry(key, keyValue, cwd); continue; } yield [key, val]; } } /** * Given an export map and all of the crazy variations, condense down to a key/value map of string keys to string values. */ export function explodeExportMap( exportField: ExportField | undefined, {cwd}: {cwd: string}, ): Record | undefined { if (!exportField) { return; } const cleanExportMap: Record = {}; for (const [key, val] of forEachExportEntryExploded(exportField, cwd)) { // If entry is an array, assume that we can always support the first value const firstVal = Array.isArray(val) ? val[0] : val; // Support these entries, in this order. const cleanValue = findExportMapEntry(firstVal); if (typeof cleanValue !== 'string') { continue; } cleanExportMap[key] = cleanValue; } if (Object.keys(cleanExportMap).length === 0) { return; } return cleanExportMap; } ================================================ FILE: esinstall/src/index.ts ================================================ import rollupPluginCommonjs, {RollupCommonJSOptions} from '@rollup/plugin-commonjs'; import rollupPluginJson from '@rollup/plugin-json'; import rollupPluginNodeResolve from '@rollup/plugin-node-resolve'; import {init as initESModuleLexer} from 'es-module-lexer'; import fs from 'fs'; import * as colors from 'kleur/colors'; import mkdirp from 'mkdirp'; import path from 'path'; import rimraf from 'rimraf'; import {InputOptions, OutputOptions, Plugin as RollupPlugin, rollup, RollupError} from 'rollup'; import rollupPluginNodePolyfills from 'rollup-plugin-polyfill-node'; import rollupPluginReplace from '@rollup/plugin-replace'; import {rollupPluginAlias} from './rollup-plugins/rollup-plugin-alias'; import {rollupPluginCatchFetch} from './rollup-plugins/rollup-plugin-catch-fetch'; import {rollupPluginCatchUnresolved} from './rollup-plugins/rollup-plugin-catch-unresolved'; import {rollupPluginCss} from './rollup-plugins/rollup-plugin-css'; import {rollupPluginNodeProcessPolyfill} from './rollup-plugins/rollup-plugin-node-process-polyfill'; import {rollupPluginDependencyStats} from './rollup-plugins/rollup-plugin-stats'; import {rollupPluginStripSourceMapping} from './rollup-plugins/rollup-plugin-strip-source-mapping'; import {rollupPluginWrapInstallTargets} from './rollup-plugins/rollup-plugin-wrap-install-targets'; import { AbstractLogger, DependencyStatsOutput, EnvVarReplacements, ImportMap, InstallTarget, } from './types'; import { createInstallTarget, findMatchingAliasEntry, getWebDependencyName, getWebDependencyType, isJavaScript, MISSING_PLUGIN_SUGGESTIONS, sanitizePackageName, writeLockfile, } from './util'; import {resolveEntrypoint, MAIN_FIELDS} from './entrypoints'; export * from './types'; export { findExportMapEntry, findManifestEntry, resolveEntrypoint, explodeExportMap, } from './entrypoints'; export {resolveDependencyManifest} from './util'; export {printStats} from './stats'; type DependencyLoc = { type: 'BUNDLE' | 'ASSET' | 'DTS'; loc: string; }; function isImportOfPackage(importUrl: string, packageName: string) { return packageName === importUrl || importUrl.startsWith(packageName + '/'); } /** * Resolve a "webDependencies" input value to the correct absolute file location. * Supports both npm package names, and file paths relative to the node_modules directory. * Follows logic similar to Node's resolution logic, but using a package.json's ESM "module" * field instead of the CJS "main" field. */ function resolveWebDependency( dep: string, resolveOptions: {cwd: string; packageLookupFields: string[]}, ): DependencyLoc { const loc = resolveEntrypoint(dep, resolveOptions); return { loc, type: getWebDependencyType(loc), }; } function generateEnvObject(userEnv: EnvVarReplacements): Object { return { NODE_ENV: userEnv.NODE_ENV || process.env.NODE_ENV || 'production', ...Object.keys(userEnv).reduce((acc, key) => { const value = userEnv[key]; acc[key] = value === true ? process.env[key] : value; return acc; }, {}), }; } function generateReplacements(env: Object): {[key: string]: string} { return Object.keys(env).reduce( (acc, key) => { acc[`process.env.${key}`] = JSON.stringify(env[key]); return acc; }, { // Other find & replacements: }, ); } interface InstallOptions { cwd: string; stats: boolean; alias: Record; importMap?: ImportMap; logger: AbstractLogger; verbose?: boolean; dest: string; env: EnvVarReplacements; treeshake?: boolean; polyfillNode: boolean; sourcemap?: boolean | 'inline'; external: string[]; externalEsm: boolean | string[] | ((imp: string) => boolean); packageLookupFields: string[]; packageExportLookupFields: string[]; // @deprecated No longer needed, all packages now have the highest fidelity named export support possible namedExports: string[]; rollup: { context?: string; plugins?: RollupPlugin[]; // for simplicity, only Rollup plugins are supported for now dedupe?: string[]; }; } type PublicInstallOptions = Partial; export {PublicInstallOptions as InstallOptions}; export type InstallResult = {importMap: ImportMap; stats: DependencyStatsOutput | null}; const FAILED_INSTALL_MESSAGE = (message?: string) => !message ? 'Install failed.' : `Install failed ${message}.`; function setOptionDefaults(_options: PublicInstallOptions): InstallOptions { if ((_options as any).lockfile) { throw new Error('[esinstall@1.0.0] option `lockfile` was renamed to `importMap`.'); } if ((_options as any).sourceMap) { throw new Error('[esinstall@1.0.0] option `sourceMap` was renamed to `sourcemap`.'); } if ((_options as any).externalPackage) { throw new Error('[esinstall@1.0.0] option `externalPackage` was renamed to `external`.'); } if ((_options as any).externalPackageEsm) { throw new Error('[esinstall@1.0.0] option `externalPackageEsm` was renamed to `externalEsm`.'); } const options = { cwd: process.cwd(), alias: {}, logger: { debug: () => {}, // silence debug messages by default log: console.log, warn: console.warn, error: console.error, }, // TODO: Make this default to false in a v2.0 release stats: true, dest: 'web_modules', external: [] as string[], externalEsm: [] as string[], polyfillNode: false, packageLookupFields: [], packageExportLookupFields: [], env: {}, namedExports: [], rollup: { plugins: [], dedupe: [], }, ..._options, }; options.dest = path.resolve(options.cwd, options.dest); return options; } export async function install( _installTargets: (InstallTarget | string)[], _options: PublicInstallOptions = {}, ): Promise { const { cwd, alias: installAlias, importMap: _importMap, logger, dest: destLoc, external, externalEsm, sourcemap, env: userEnv, stats, rollup: userDefinedRollup, treeshake: isTreeshake, polyfillNode, packageLookupFields, packageExportLookupFields, } = setOptionDefaults(_options); const env = generateEnvObject(userEnv); const installTargets: InstallTarget[] = _installTargets.map((t) => typeof t === 'string' ? createInstallTarget(t) : t, ); // TODO: warn if import from "firebase", since that includes so many Node-specific files const allInstallSpecifiers = new Set( installTargets .filter( (dep) => !external.some((packageName) => isImportOfPackage(dep.specifier, packageName)), ) .map((dep) => dep.specifier) .map((specifier) => { const aliasEntry = findMatchingAliasEntry(installAlias, specifier); return aliasEntry && aliasEntry.type === 'package' ? aliasEntry.to : specifier; }) .map((specifier) => specifier.replace(/(\/|\\)+$/, '')) // remove trailing slash from end of specifier (easier for Node to resolve) .sort((a, b) => a.localeCompare(b, undefined, {numeric: true})), ); const installEntrypoints: {[targetName: string]: string} = {}; const assetEntrypoints: {[targetName: string]: string} = {}; const importMap: ImportMap = {imports: {}}; let dependencyStats: DependencyStatsOutput | null = null; const skipFailures = false; for (const installSpecifier of allInstallSpecifiers) { let targetName = getWebDependencyName(installSpecifier); let proxiedName = sanitizePackageName(targetName); // sometimes we need to sanitize webModule names, as in the case of tippy.js -> tippyjs if (_importMap) { if (_importMap.imports[installSpecifier]) { installEntrypoints[targetName] = _importMap.imports[installSpecifier]; if (!path.extname(installSpecifier) || isJavaScript(installSpecifier)) { importMap.imports[installSpecifier] = `./${proxiedName}.js`; } else { importMap.imports[installSpecifier] = `./${proxiedName}`; } continue; } const findFolderImportEntry = Object.entries(_importMap.imports).find(([entry]) => { return entry.endsWith('/') && installSpecifier.startsWith(entry); }); if (findFolderImportEntry) { installEntrypoints[targetName] = findFolderImportEntry[1] + targetName.substr(findFolderImportEntry[0].length); if (!path.extname(installSpecifier) || isJavaScript(installSpecifier)) { importMap.imports[installSpecifier] = `./${proxiedName}.js`; } else { importMap.imports[installSpecifier] = `./${proxiedName}`; } continue; } } try { const resolvedResult = resolveWebDependency(installSpecifier, { cwd, packageLookupFields, }); if (resolvedResult.type === 'BUNDLE') { installEntrypoints[targetName] = resolvedResult.loc; importMap.imports[installSpecifier] = `./${proxiedName}.js`; Object.entries(installAlias) .filter(([, value]) => value === installSpecifier) .forEach(([key]) => { importMap.imports[key] = `./${targetName}.js`; }); } else if (resolvedResult.type === 'ASSET') { // add extension if missing const isMissingExt = path.extname(resolvedResult.loc) && !path.extname(proxiedName); if (isMissingExt) { const ext = path.basename(resolvedResult.loc).replace(/[^.]+/, ''); targetName += ext; proxiedName += ext; } assetEntrypoints[targetName] = resolvedResult.loc; importMap.imports[installSpecifier] = `./${proxiedName}`; } else if (resolvedResult.type === 'DTS') { // This is fine! Skip type-only packages logger.debug(`[${installSpecifier}] target points to a TS-only package.`); } } catch (err) { if (skipFailures) { continue; } throw err; } } if (Object.keys(installEntrypoints).length === 0 && Object.keys(assetEntrypoints).length === 0) { throw new Error(`No ESM dependencies found! ${colors.dim( ` At least one dependency must have an ESM "module" entrypoint. You can find modern, web-ready packages at ${colors.underline( 'https://www.skypack.dev', )}`, )}`); } await initESModuleLexer; let isFatalWarningFound = false; const inputOptions: InputOptions = { input: installEntrypoints, context: userDefinedRollup.context, external: (id) => external.some((packageName) => isImportOfPackage(id, packageName)), treeshake: {moduleSideEffects: true}, plugins: [ rollupPluginAlias({ entries: [ // Apply all aliases ...Object.entries(installAlias).map(([key, val]) => ({ find: key, replacement: val, exact: false, })), // Make sure that internal imports also honor any resolved installEntrypoints ...Object.entries(installEntrypoints).map(([key, val]) => ({ find: key, replacement: val, exact: true, })), ], }), rollupPluginCatchFetch(), rollupPluginNodeResolve({ mainFields: [...packageLookupFields, ...MAIN_FIELDS], extensions: ['.mjs', '.cjs', '.js', '.json'], // Default: [ '.mjs', '.js', '.json', '.node' ] // whether to prefer built-in modules (e.g. `fs`, `path`) or local ones with the same names preferBuiltins: true, // Default: true dedupe: userDefinedRollup.dedupe || [], // @ts-ignore: Added in v11+ of this plugin exportConditions: packageExportLookupFields, }), rollupPluginJson({ preferConst: true, indent: ' ', compact: false, namedExports: true, }), rollupPluginCss(), rollupPluginReplace({ preventAssignment: true, values: generateReplacements(env), }), rollupPluginCommonjs({ extensions: ['.js', '.cjs'], esmExternals: Array.isArray(externalEsm) ? (id) => externalEsm.some((packageName) => isImportOfPackage(id, packageName)) : externalEsm, requireReturnsDefault: 'auto', } as RollupCommonJSOptions), rollupPluginWrapInstallTargets(!!isTreeshake, installTargets, logger), stats && rollupPluginDependencyStats((info) => (dependencyStats = info)), rollupPluginNodeProcessPolyfill(env), polyfillNode && rollupPluginNodePolyfills(), ...(userDefinedRollup.plugins || []), // load user-defined plugins last rollupPluginCatchUnresolved(), rollupPluginStripSourceMapping(), ].filter(Boolean) as Plugin[], onwarn(warning) { // Log "unresolved" import warnings as an error, causing Snowpack to fail at the end. if ( warning.code === 'PLUGIN_WARNING' && warning.plugin === 'snowpack:rollup-plugin-catch-unresolved' ) { isFatalWarningFound = true; // Display posix-style on all environments, mainly to help with CI :) if (warning.id) { logger.error(`${warning.id}\n ${warning.message}`); } else { logger.error( `${warning.message}. See https://www.snowpack.dev/reference/common-error-details`, ); } return; } const {loc, message} = warning; const logMessage = loc ? `${loc.file}:${loc.line}:${loc.column} ${message}` : message; // These warnings are usually harmless in packages, so don't show them by default. if ( warning.code === 'CIRCULAR_DEPENDENCY' || warning.code === 'NAMESPACE_CONFLICT' || warning.code === 'THIS_IS_UNDEFINED' || warning.code === 'EMPTY_BUNDLE' || warning.code === 'UNUSED_EXTERNAL_IMPORT' ) { logger.debug(logMessage); } else { logger.warn(logMessage); } }, }; const outputOptions: OutputOptions = { dir: destLoc, format: 'esm', sourcemap, exports: 'named', entryFileNames: (chunk) => { const targetName = getWebDependencyName(chunk.name); const proxiedName = sanitizePackageName(targetName); return `${proxiedName}.js`; }, chunkFileNames: 'common/[name]-[hash].js', }; rimraf.sync(destLoc); if (Object.keys(installEntrypoints).length > 0) { try { logger.debug(process.cwd()); logger.debug(`running installer with options: ${JSON.stringify(inputOptions)}`); const packageBundle = await rollup(inputOptions); logger.debug(`installing npm packages: ${Object.keys(installEntrypoints).join(', ')}`); if (isFatalWarningFound) { // We don't know exactly which package failed because it happened in rollup // but users need all the information we *do know* in order to debug const packageName = Object.keys(installEntrypoints).length === 1 ? `for ${Object.keys(installEntrypoints)[0]}` : `for one of ${Object.keys(installEntrypoints).join(', ')}`; throw new Error(FAILED_INSTALL_MESSAGE(packageName)); } logger.debug(`writing install results to disk`); await packageBundle.write(outputOptions); } catch (_err) { logger.debug(`FAILURE: ${_err}`); const err: RollupError = _err; if (err.code === 'MISSING_EXPORT') { let [exportSpecifier, tail] = err.message.split(' is not exported by '); exportSpecifier = exportSpecifier.slice(1, -1); const specifier = tail.split('imported by ')[1]; let modName; for (const [key, value] of Object.entries(installEntrypoints)) { if (value === specifier) { modName = key; break; } } throw new Error( `Module "${modName}" has no exported member "${exportSpecifier}". Did you mean to use "import ${exportSpecifier} from '${modName}'" instead?`, ); } const errFilePath = err.loc?.file || err.id; if (!errFilePath) { throw err; } // NOTE: Rollup will fail instantly on most errors. Therefore, we can // only report one error at a time. `err.watchFiles` also exists, but // for now `err.loc.file` and `err.id` have all the info that we need. const failedExtension = path.extname(errFilePath); const suggestion = MISSING_PLUGIN_SUGGESTIONS[failedExtension] || err.message; // Display posix-style on all environments, mainly to help with CI :) const fileName = path.relative(cwd, errFilePath).replace(/\\/g, '/'); logger.error(`Failed to load ${colors.bold(fileName)}\n ${suggestion}`); throw new Error(FAILED_INSTALL_MESSAGE()); } } mkdirp.sync(destLoc); await writeLockfile(path.join(destLoc, 'import-map.json'), importMap); for (const [assetName, assetLoc] of Object.entries(assetEntrypoints)) { const assetDest = `${destLoc}/${sanitizePackageName(assetName)}`; mkdirp.sync(path.dirname(assetDest)); fs.copyFileSync(assetLoc, assetDest); } return { importMap, stats: dependencyStats, }; } ================================================ FILE: esinstall/src/rollup-plugins/generateProcessPolyfill.ts ================================================ /* (The MIT License) Copyright (c) 2013 Roman Shtylman Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /* THIS IS A MODIFIED VERSION OF https://github.com/calvinmetcalf/node-process-es6 ORIGIANL ADDED IN COMMIT 6304406e065f356aeaa623a878d02be419b316d8 (good to know for diffing) */ export function generateProcessPolyfill(env) { return `/* SNOWPACK PROCESS POLYFILL (based on https://github.com/calvinmetcalf/node-process-es6) */ function defaultSetTimout() { throw new Error('setTimeout has not been defined'); } function defaultClearTimeout () { throw new Error('clearTimeout has not been defined'); } var cachedSetTimeout = defaultSetTimout; var cachedClearTimeout = defaultClearTimeout; var globalContext; if (typeof window !== 'undefined') { globalContext = window; } else if (typeof self !== 'undefined') { globalContext = self; } else { globalContext = {}; } if (typeof globalContext.setTimeout === 'function') { cachedSetTimeout = setTimeout; } if (typeof globalContext.clearTimeout === 'function') { cachedClearTimeout = clearTimeout; } function runTimeout(fun) { if (cachedSetTimeout === setTimeout) { //normal enviroments in sane situations return setTimeout(fun, 0); } // if setTimeout wasn't available but was latter defined if ((cachedSetTimeout === defaultSetTimout || !cachedSetTimeout) && setTimeout) { cachedSetTimeout = setTimeout; return setTimeout(fun, 0); } try { // when when somebody has screwed with setTimeout but no I.E. maddness return cachedSetTimeout(fun, 0); } catch(e){ try { // When we are in I.E. but the script has been evaled so I.E. doesn't trust the global object when called normally return cachedSetTimeout.call(null, fun, 0); } catch(e){ // same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error return cachedSetTimeout.call(this, fun, 0); } } } function runClearTimeout(marker) { if (cachedClearTimeout === clearTimeout) { //normal enviroments in sane situations return clearTimeout(marker); } // if clearTimeout wasn't available but was latter defined if ((cachedClearTimeout === defaultClearTimeout || !cachedClearTimeout) && clearTimeout) { cachedClearTimeout = clearTimeout; return clearTimeout(marker); } try { // when when somebody has screwed with setTimeout but no I.E. maddness return cachedClearTimeout(marker); } catch (e){ try { // When we are in I.E. but the script has been evaled so I.E. doesn't trust the global object when called normally return cachedClearTimeout.call(null, marker); } catch (e){ // same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error. // Some versions of I.E. have different rules for clearTimeout vs setTimeout return cachedClearTimeout.call(this, marker); } } } var queue = []; var draining = false; var currentQueue; var queueIndex = -1; function cleanUpNextTick() { if (!draining || !currentQueue) { return; } draining = false; if (currentQueue.length) { queue = currentQueue.concat(queue); } else { queueIndex = -1; } if (queue.length) { drainQueue(); } } function drainQueue() { if (draining) { return; } var timeout = runTimeout(cleanUpNextTick); draining = true; var len = queue.length; while(len) { currentQueue = queue; queue = []; while (++queueIndex < len) { if (currentQueue) { currentQueue[queueIndex].run(); } } queueIndex = -1; len = queue.length; } currentQueue = null; draining = false; runClearTimeout(timeout); } function nextTick(fun) { var args = new Array(arguments.length - 1); if (arguments.length > 1) { for (var i = 1; i < arguments.length; i++) { args[i - 1] = arguments[i]; } } queue.push(new Item(fun, args)); if (queue.length === 1 && !draining) { runTimeout(drainQueue); } } // v8 likes predictible objects function Item(fun, array) { this.fun = fun; this.array = array; } Item.prototype.run = function () { this.fun.apply(null, this.array); }; var title = 'browser'; var platform = 'browser'; var browser = true; var env = {}; var argv = []; var version = ''; // empty string to avoid regexp issues var versions = {}; var release = {}; var config = {}; function noop() {} var on = noop; var addListener = noop; var once = noop; var off = noop; var removeListener = noop; var removeAllListeners = noop; var emit = noop; function binding(name) { throw new Error('process.binding is not supported'); } function cwd () { return '/' } function chdir (dir) { throw new Error('process.chdir is not supported'); }function umask() { return 0; } // from https://github.com/kumavis/browser-process-hrtime/blob/master/index.js var performance = globalContext.performance || {}; var performanceNow = performance.now || performance.mozNow || performance.msNow || performance.oNow || performance.webkitNow || function(){ return (new Date()).getTime() }; // generate timestamp or delta // see http://nodejs.org/api/process.html#process_process_hrtime function hrtime(previousTimestamp){ var clocktime = performanceNow.call(performance)*1e-3; var seconds = Math.floor(clocktime); var nanoseconds = Math.floor((clocktime%1)*1e9); if (previousTimestamp) { seconds = seconds - previousTimestamp[0]; nanoseconds = nanoseconds - previousTimestamp[1]; if (nanoseconds<0) { seconds--; nanoseconds += 1e9; } } return [seconds,nanoseconds] } var startTime = new Date(); function uptime() { var currentTime = new Date(); var dif = currentTime - startTime; return dif / 1000; } export default { nextTick: nextTick, title: title, browser: browser, env: ${JSON.stringify(env)}, argv: argv, version: version, versions: versions, on: on, addListener: addListener, once: once, off: off, removeListener: removeListener, removeAllListeners: removeAllListeners, emit: emit, binding: binding, cwd: cwd, chdir: chdir, umask: umask, hrtime: hrtime, platform: platform, release: release, config: config, uptime: uptime }; export { addListener, argv, binding, browser, chdir, config, cwd, emit, env, hrtime, nextTick, off, on, once, platform, release, removeAllListeners, removeListener, title, umask, uptime, version, versions }; `; } export default generateProcessPolyfill; ================================================ FILE: esinstall/src/rollup-plugins/rollup-plugin-alias.ts ================================================ /** This plugin was forked from the https://github.com/rollup/plugins/tree/master/packages/alias package: The MIT License (MIT) Copyright (c) 2019 RollupJS Plugin Contributors (https://github.com/rollup/plugins/graphs/contributors) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ import {platform} from 'os'; import slash from 'slash'; import type {PartialResolvedId, Plugin} from 'rollup'; const VOLUME = /^([A-Z]:)/i; const IS_WINDOWS = platform() === 'win32'; const noop = () => null; function matches(alias: Alias, importee: string) { if (alias.find instanceof RegExp) { return alias.find.test(importee); } if (importee.length < alias.find.length) { return false; } if (importee === alias.find) { return true; } if (alias.exact) { return false; } const importeeStartsWithKey = importee.indexOf(alias.find) === 0; const importeeHasSlashAfterKey = importee.substring(alias.find.length)[0] === '/'; return importeeStartsWithKey && importeeHasSlashAfterKey; } function normalizeId(id: string): string; function normalizeId(id: string | undefined): string | undefined; function normalizeId(id: string | undefined) { if (typeof id === 'string' && (IS_WINDOWS || VOLUME.test(id))) { return slash(id.replace(VOLUME, '')); } return id; } interface Alias { find: string | RegExp; replacement: string; exact: boolean; } function getEntries({entries}): readonly Alias[] { if (!entries) { return []; } return entries; } export function rollupPluginAlias(options: {entries: Alias[]}): Plugin { const entries = getEntries(options); if (entries.length === 0) { return { name: 'alias', resolveId: noop, }; } return { name: 'alias', resolveId(importee, importer) { const importeeId = normalizeId(importee); const importerId = normalizeId(importer); // First match is supposed to be the correct one const matchedEntry = entries.find((entry) => matches(entry, importeeId)); if (!matchedEntry || !importerId) { return null; } const updatedId = normalizeId( importeeId.replace(matchedEntry.find, matchedEntry.replacement), ); return this.resolve(updatedId, importer, {skipSelf: true}).then((resolved) => { let finalResult: PartialResolvedId | null = resolved; if (!finalResult) { finalResult = {id: updatedId}; } return finalResult; }); }, }; } ================================================ FILE: esinstall/src/rollup-plugins/rollup-plugin-catch-fetch.ts ================================================ import path from 'path'; import {Plugin} from 'rollup'; const FETCH_POLYFILL = ` // native patch for: node-fetch, whatwg-fetch // ref: https://github.com/tc39/proposal-global var getGlobal = function () { if (typeof self !== 'undefined') { return self; } if (typeof window !== 'undefined') { return window; } if (typeof global !== 'undefined') { return global; } throw new Error('unable to locate global object'); } var global = getGlobal(); export default global.fetch.bind(global); export const Headers = global.Headers; export const Request = global.Request; export const Response = global.Response; `; /** * rollup-plugin-catch-fetch * * How it works: NPM packages will sometimes contain Node.js-specific polyfills * for the native browser Fetch API. Since this makes no sense in an ESM web * project, we can replace these expensive polyfills with native references to * the fetch API. * * This still allows you to polyfill fetch in older browsers, if you desire. */ function isNodeFetch(id: string): boolean { return ( id === 'node-fetch' || id === 'whatwg-fetch' || id.includes(path.join('node_modules', 'node-fetch')) || // note: sometimes Snowpack has found the entry file already id.includes(path.join('node_modules', 'whatwg-fetch')) ); } export function rollupPluginCatchFetch(): Plugin { return { name: 'snowpack:fetch-handler', resolveId(id) { return isNodeFetch(id) ? id : null; }, load(id) { return isNodeFetch(id) ? FETCH_POLYFILL : null; }, }; } ================================================ FILE: esinstall/src/rollup-plugins/rollup-plugin-catch-unresolved.ts ================================================ import builtinModules from 'builtin-modules'; import {Plugin} from 'rollup'; /** * rollup-plugin-catch-unresolved * * Catch any unresolved imports to give proper warnings (Rollup default is to ignore). */ export function rollupPluginCatchUnresolved(): Plugin { return { name: 'snowpack:rollup-plugin-catch-unresolved', resolveId(id, importer) { // Ignore remote http/https imports if (id.startsWith('http://') || id.startsWith('https://')) { return false; } if (builtinModules.indexOf(id) !== -1) { this.warn({ id: importer, message: `Module "${id}" (Node.js built-in) is not available in the browser. Run Snowpack with --polyfill-node to fix.`, }); } else if (id.startsWith('./') || id.startsWith('../')) { this.warn({ id: importer, message: `Import "${id}" could not be resolved from file.`, }); } else { this.warn({ id: importer, message: `Module "${id}" could not be resolved by Snowpack (Is it installed?).`, }); } return false; }, }; } ================================================ FILE: esinstall/src/rollup-plugins/rollup-plugin-css.ts ================================================ import {Plugin} from 'rollup'; function getInjectorCode(name: string, code: string) { return ` /** SNOWPACK INJECT STYLE: ${name} */ function __snowpack__injectStyle(css) { const headEl = document.head || document.getElementsByTagName('head')[0]; const styleEl = document.createElement('style'); styleEl.type = 'text/css'; if (styleEl.styleSheet) { styleEl.styleSheet.cssText = css; } else { styleEl.appendChild(document.createTextNode(css)); } headEl.appendChild(styleEl); } __snowpack__injectStyle(${JSON.stringify(code)});\n`; } /** * rollup-plugin-css * * Support installing any imported CSS into your dependencies. This isn't strictly valid * ESM code, but it is popular in the npm ecosystem & web development ecosystems. It also * solves a problem that is difficult to solve otherwise (referencing CSS from JS) so for * those reasons we have added default support for importing CSS into Snowpack v2. */ export function rollupPluginCss() { return { name: 'snowpack:rollup-plugin-css', async transform(code: string, id: string) { if (!id.endsWith('.css')) { return null; } const humanReadableName = id.replace(/.*node_modules[\/\\]/, '').replace(/[\/\\]/g, '/'); return getInjectorCode(humanReadableName, code); }, } as Plugin; } ================================================ FILE: esinstall/src/rollup-plugins/rollup-plugin-node-process-polyfill.ts ================================================ import inject from '@rollup/plugin-inject'; import {Plugin} from 'rollup'; import generateProcessPolyfill from './generateProcessPolyfill'; const PROCESS_MODULE_NAME = 'process'; export function rollupPluginNodeProcessPolyfill(env = {}): Plugin { const injectPlugin = inject({ process: PROCESS_MODULE_NAME, include: /\.(cjs|js|jsx|mjs|ts|tsx)$/, // only target JavaScript files }); return { ...injectPlugin, name: 'snowpack:rollup-plugin-node-process-polyfill', resolveId(source) { if (source === PROCESS_MODULE_NAME) { return PROCESS_MODULE_NAME; } return null; }, load(id) { if (id === PROCESS_MODULE_NAME) { return {code: generateProcessPolyfill(env), moduleSideEffects: false}; } return null; }, }; } ================================================ FILE: esinstall/src/rollup-plugins/rollup-plugin-stats.ts ================================================ import fs from 'fs'; import path from 'path'; import {OutputBundle, Plugin} from 'rollup'; import zlib from 'zlib'; import {DependencyType, DependencyStatsOutput} from '../types'; export function rollupPluginDependencyStats( cb: (dependencyInfo: DependencyStatsOutput) => void, ): Plugin { let outputDir: string; let existingFileCache: {[fileName: string]: number} = {}; let statsSummary: DependencyStatsOutput = { direct: {}, common: {}, }; function buildExistingFileCache(bundle: OutputBundle) { for (let fileName of Object.keys(bundle)) { const filePath = path.join(outputDir, fileName); if (fs.existsSync(filePath)) { const {size} = fs.statSync(filePath); existingFileCache[fileName] = size; } } } function compareDependencies( files: {fileName: string; contents: Buffer}[], type: DependencyType, ) { for (let {fileName, contents} of files) { const size = contents.byteLength; statsSummary[type][fileName] = { size: size, gzip: zlib.gzipSync(contents).byteLength, brotli: zlib.brotliCompressSync ? zlib.brotliCompressSync(contents).byteLength : 0, }; if (existingFileCache[fileName]) { const delta = (size - existingFileCache[fileName]) / 1000; statsSummary[type][fileName].delta = delta; } } } return { name: 'snowpack:rollup-plugin-stats', generateBundle(options, bundle) { outputDir = options.dir!; buildExistingFileCache(bundle); }, writeBundle(_, bundle) { const directDependencies: {fileName: string; contents: Buffer}[] = []; const commonDependencies: {fileName: string; contents: Buffer}[] = []; for (const [fileName, assetOrChunk] of Object.entries(bundle)) { const raw = assetOrChunk.type === 'asset' ? assetOrChunk.source : assetOrChunk.code; const contents = Buffer.isBuffer(raw) ? raw : typeof raw === 'string' ? Buffer.from(raw, 'utf8') : Buffer.from(raw); if (fileName.startsWith('common')) { commonDependencies.push({fileName, contents}); } else { directDependencies.push({fileName, contents}); } } compareDependencies(directDependencies, 'direct'); compareDependencies(commonDependencies, 'common'); cb(statsSummary); }, }; } ================================================ FILE: esinstall/src/rollup-plugins/rollup-plugin-strip-source-mapping.ts ================================================ import {Plugin} from 'rollup'; /** * rollup-plugin-strip-source-mapping * * Remove any lingering source map comments */ export function rollupPluginStripSourceMapping(): Plugin { return { name: 'snowpack:rollup-plugin-strip-source-mapping', transform: (code) => ({ code: code // [a-zA-Z0-9-_\*?\.\/\&=+%]: valid URL characters (for sourcemaps) .replace(/\/\/#\s*sourceMappingURL=[a-zA-Z0-9-_\*\?\.\/\&=+%\s]+$/gm, ''), map: null, }), }; } ================================================ FILE: esinstall/src/rollup-plugins/rollup-plugin-wrap-install-targets.ts ================================================ import fs from 'fs'; import path from 'path'; import {Plugin} from 'rollup'; import execa from 'execa'; import {VM as VM2} from 'vm2'; import {AbstractLogger, InstallTarget} from '../types'; import {getWebDependencyName, isJavaScript, isRemoteUrl, isTruthy} from '../util'; import isValidIdentifier from 'is-valid-identifier'; import resolve from 'resolve'; // Use CJS intentionally here! ESM interface is async but CJS is sync, and this file is sync const {parse} = require('cjs-module-lexer'); function isValidNamedExport(name: string): boolean { return name !== 'default' && name !== '__esModule' && isValidIdentifier(name); } // Add popular CJS/UMD packages here that use "synthetic" named imports in their documentation. // Our scanner can statically scan most packages without an opt-in here, but these packages // are built oddly, in a way that we can't statically analyze. const TRUSTED_CJS_PACKAGES = ['chai/index.js', 'events/events.js', 'uuid/index.js']; // These packages are written in such a way that the official CJS scanner succeeds at scanning // the file but fails to pick up some exports. Add popular packages here to save everyone a bit // of headache. // We use the exact file here to match the official package, but not any ESM aliase packages // that the user may have installed instead (ex: react-esm). const UNSCANNABLE_CJS_PACKAGES = [ 'chai/index.js', 'events/events.js', 'property-expr/index.js', // Note: resolved in v4.x release 'react-transition-group/index.js', ]; /** * rollup-plugin-wrap-install-targets * * How it works: * 1. An array of "install targets" are passed in, describing all known imports + metadata. * 2. If isTreeshake: Known imports are marked for tree-shaking by appending 'snowpack-wrap:' to the input value. * 3. If autoDetectPackageExports match: Also mark for wrapping, and use automatic export detection. * 4. On load, we return a false virtual file for all "snowpack-wrap:" inputs. * a. That virtual file contains only `export ... from 'ACTUAL_FILE_PATH';` exports * b. Rollup uses those exports to drive its tree-shaking algorithm. * c. Rollup uses those exports to inform its "namedExports" for Common.js entrypoints. */ export function rollupPluginWrapInstallTargets( isTreeshake: boolean, installTargets: InstallTarget[], logger: AbstractLogger, ): Plugin { const installTargetSummaries: {[loc: string]: InstallTarget} = {}; const cjsScannedNamedExports = new Map(); /** * Attempt #1: Static analysis: Lower Fidelity, but faster. * Do our best job to statically scan a file for named exports. This uses "cjs-module-lexer", the * same CJS export scanner that Node.js uses internally. Very fast, but only works on some modules, * depending on how they were build/written/compiled. */ function cjsAutoDetectExportsStatic(filename: string, visited = new Set()): string[] | undefined { const isMainEntrypoint = visited.size === 0; // Prevent infinite loops via circular dependencies. if (visited.has(filename)) { return []; } else { visited.add(filename); } const fileContents = fs.readFileSync(filename, 'utf8'); try { // Attempt 1 - CJS: Run cjs-module-lexer to statically analyze exports. let {exports, reexports} = parse(fileContents); // If re-exports were detected (`exports.foo = require(...)`) then resolve them here. let resolvedReexports: string[] = []; if (reexports.length > 0) { resolvedReexports = ([] as string[]).concat.apply( [], reexports .map((e) => cjsAutoDetectExportsStatic( resolve.sync(e, {basedir: path.dirname(filename)}), visited, ), ) .filter(isTruthy), ); } // If nothing was detected, return undefined. // Otherwise, resolve and flatten all exports into a single array, remove invalid exports. const resolvedExports = Array.from(new Set([...exports, ...resolvedReexports])).filter( isValidNamedExport, ); return isMainEntrypoint && resolvedExports.length === 0 ? undefined : resolvedExports; } catch (err) { // Safe to ignore, this is usually due to the file not being CJS. logger.debug(`cjsAutoDetectExportsStatic ${filename}: ${err.message}`); } } /** * Attempt #2a - Runtime analysis: More powerful, but slower. (trusted code) * This function spins off a Node.js process to analyze the most accurate set of named imports that this * module supports. If this fails, there's not much else possible that we could do. * * We consider this "trusted" because it will actually run the package code in Node.js on your machine. * Since this is code that you are intentionally bundling into your application, we consider this fine * for most users and equivilent to the current security story of Node.js/npm. But, if you are operating * a service that runs esinstall on arbitrary code, you should set `process.env.ESINSTALL_UNTRUSTED_ENVIRONMENT` * so that this is skipped. */ function cjsAutoDetectExportsRuntimeTrusted(normalizedFileName: string): string[] | undefined { // Skip if set to not trust package code (besides a few popular, always-trusted packages). if ( process.env.ESINSTALL_UNTRUSTED_ENVIRONMENT && !TRUSTED_CJS_PACKAGES.includes(normalizedFileName) ) { return undefined; } try { const {stdout} = execa.sync( `node`, ['-p', `JSON.stringify(Object.keys(require('${normalizedFileName}')))`], { cwd: __dirname, extendEnv: false, }, ); const exportsResult = JSON.parse(stdout).filter(isValidNamedExport); logger.debug(`cjsAutoDetectExportsRuntime success ${normalizedFileName}: ${exportsResult}`); return exportsResult; } catch (err) { logger.debug(`cjsAutoDetectExportsRuntime error ${normalizedFileName}: ${err.message}`); } } /** * Attempt #2b - Sandboxed runtime analysis: More powerful, but slower. * This will only work on UMD and very simple CJS files (require not supported). * Uses VM2 to run safely sandbox untrusted code (no access no Node.js primitives, just JS). * If nothing was detected, return undefined. */ function cjsAutoDetectExportsRuntimeUntrusted(filename: string): string[] | undefined { try { const fileContents = fs.readFileSync(filename, 'utf8'); const vm = new VM2({wasm: false, fixAsync: false}); const exportsResult = Object.keys( vm.run('const exports={}; const module={exports}; ' + fileContents + ';; module.exports;'), ); logger.debug(`cjsAutoDetectExportsRuntimeUntrusted success ${filename}: ${exportsResult}`); return exports.filter((identifier) => isValidIdentifier(identifier)); } catch (err) { logger.debug(`cjsAutoDetectExportsRuntimeUntrusted error ${filename}: ${err.message}`); } } return { name: 'snowpack:wrap-install-targets', // Mark some inputs for tree-shaking. buildStart(inputOptions) { const input = inputOptions.input as {[entryAlias: string]: string}; for (const [key, val] of Object.entries(input)) { if (isRemoteUrl(val)) { continue; } if (!isJavaScript(val)) { continue; } const allInstallTargets = installTargets.filter( (imp) => getWebDependencyName(imp.specifier) === key, ); const installTargetSummary = allInstallTargets.reduce((summary, imp) => { summary.all = summary.all || imp.all; summary.default = summary.default || imp.default || imp.all; summary.namespace = summary.namespace || imp.namespace || imp.all; summary.named = [...(summary.named || []), ...imp.named]; return summary; }, {} as any); installTargetSummaries[val] = installTargetSummary; const normalizedFileLoc = val.split(path.win32.sep).join(path.posix.sep); const knownBadPackage = UNSCANNABLE_CJS_PACKAGES.some((p) => normalizedFileLoc.includes(`node_modules/${p}${p.endsWith('.js') ? '' : '/'}`), ); const cjsExports = // If we can trust the static analyzer, run that first. (!knownBadPackage && cjsAutoDetectExportsStatic(val)) || // Otherwise, run our more powerful, runtime analysis. // Attempted trusted first (won't run in untrusted environments). cjsAutoDetectExportsRuntimeTrusted(normalizedFileLoc) || cjsAutoDetectExportsRuntimeUntrusted(normalizedFileLoc); if (cjsExports && cjsExports.length > 0) { cjsScannedNamedExports.set(normalizedFileLoc, cjsExports); input[key] = `snowpack-wrap:${val}`; } if (isTreeshake && !installTargetSummary.all) { input[key] = `snowpack-wrap:${val}`; } } }, resolveId(source) { if (source.startsWith('snowpack-wrap:')) { return source; } return null; }, load(id) { if (!id.startsWith('snowpack-wrap:')) { return null; } const fileLoc = id.substring('snowpack-wrap:'.length); // Reduce all install targets into a single "summarized" install target. const installTargetSummary = installTargetSummaries[fileLoc]; let uniqueNamedExports = Array.from(new Set(installTargetSummary.named)); const normalizedFileLoc = fileLoc.split(path.win32.sep).join(path.posix.sep); const scannedNamedExports = cjsScannedNamedExports.get(normalizedFileLoc); if (scannedNamedExports && (!isTreeshake || installTargetSummary.namespace)) { uniqueNamedExports = scannedNamedExports || []; installTargetSummary.default = true; } const result = ` ${installTargetSummary.namespace ? `export * from '${normalizedFileLoc}';` : ''} ${ installTargetSummary.default ? `import __pika_web_default_export_for_treeshaking__ from '${normalizedFileLoc}'; export default __pika_web_default_export_for_treeshaking__;` : '' } ${`export {${uniqueNamedExports.join(',')}} from '${normalizedFileLoc}';`} `; return result; }, }; } ================================================ FILE: esinstall/src/stats.ts ================================================ import * as colors from 'kleur/colors'; import {DependencyStats, DependencyStatsOutput} from './types'; /** The minimum width, in characters, of each size column */ const SIZE_COLUMN_WIDTH = 11; /** Generic Object.entries() alphabetical sort by keys. */ function entriesSort([filenameA]: [string, any], [filenameB]: [string, any]) { return filenameA.localeCompare(filenameB); } /** Pretty-prints number of bytes as "XXX KB" */ function formatSize(size) { let kb = Math.round((size / 1000) * 100) / 100; if (kb >= 1000) { kb = Math.floor(kb); } let color; if (kb < 15) { color = 'green'; } else if (kb < 30) { color = 'yellow'; } else { color = 'red'; } return colors[color](`${kb} KB`.padEnd(SIZE_COLUMN_WIDTH)); } function formatDelta(delta) { const kb = Math.round(delta * 100) / 100; const color = delta > 0 ? 'red' : 'green'; return colors[color](`Δ ${delta > 0 ? '+' : ''}${kb} KB`); } function formatFileInfo( filename: string, stats: DependencyStats, padEnd: number, isLastFile: boolean, ): string { const lineGlyph = colors.dim(isLastFile ? '└─' : '├─'); const lineName = filename.padEnd(padEnd); const fileStat = formatSize(stats.size); const gzipStat = formatSize(stats.gzip); const brotliStat = formatSize(stats.brotli); const lineStat = fileStat + gzipStat + brotliStat; let lineDelta = ''; if (stats.delta) { lineDelta = colors.dim('[') + formatDelta(stats.delta) + colors.dim(']'); } // Trim trailing whitespace (can mess with formatting), but keep indentation. return ` ` + `${lineGlyph} ${lineName} ${lineStat} ${lineDelta}`.trim(); } function formatFiles(files: [string, DependencyStats][], padEnd: number) { const strippedFiles = files.map(([filename, stats]) => [ filename.replace(/^common\//, ''), stats, ]) as [string, DependencyStats][]; return strippedFiles .map(([filename, stats], index) => formatFileInfo(filename, stats, padEnd, index >= files.length - 1), ) .join('\n'); } export function printStats(dependencyStats: DependencyStatsOutput): string { let output = ''; const {direct, common} = dependencyStats; const allDirect = Object.entries(direct).sort(entriesSort); const allCommon = Object.entries(common).sort(entriesSort); const maxFileNameLength = [...allCommon, ...allDirect].reduce( (max, [filename]) => Math.max(filename.length, max), 'web_modules/'.length, ) + 1; output += ` ⦿ ${colors.bold('web_modules/'.padEnd(maxFileNameLength + 4))}` + colors.bold(colors.underline('size'.padEnd(SIZE_COLUMN_WIDTH - 2))) + ' ' + colors.bold(colors.underline('gzip'.padEnd(SIZE_COLUMN_WIDTH - 2))) + ' ' + colors.bold(colors.underline('brotli'.padEnd(SIZE_COLUMN_WIDTH - 2))) + `\n`; output += `${formatFiles(allDirect, maxFileNameLength)}\n`; if (Object.values(common).length > 0) { output += ` ⦿ ${colors.bold('web_modules/common/ (Shared)')}\n`; output += `${formatFiles(allCommon, maxFileNameLength)}`; } return `\n${output}\n`; } ================================================ FILE: esinstall/src/types.ts ================================================ export type DeepPartial = { [P in keyof T]?: T[P] extends Array ? Array> : T[P] extends ReadonlyArray ? ReadonlyArray> : DeepPartial; }; export type EnvVarReplacements = Record; export interface ImportMap { imports: {[packageName: string]: string}; } export interface AbstractLogger { debug: (...args: any[]) => void; log: (...args: any[]) => void; warn: (...args: any[]) => void; error: (...args: any[]) => void; } /** * An install target represents information about a dependency to install. * The specifier is the key pointing to the dependency, either as a package * name or as an actual file path within node_modules. All other properties * are metadata about what is actually being imported. */ export type InstallTarget = { specifier: string; all: boolean; default: boolean; namespace: boolean; named: string[]; }; export type DependencyStats = { size: number; gzip: number; brotli?: number; delta?: number; }; export type DependencyType = 'direct' | 'common'; export type DependencyStatsMap = { [filePath: string]: DependencyStats; }; export type DependencyStatsOutput = Record; export type LoggerLevel = 'debug' | 'info' | 'warn' | 'error' | 'silent'; // same as Pino export type LoggerEvent = 'debug' | 'info' | 'warn' | 'error'; export interface LoggerOptions { /** (optional) change name at beginning of line */ name?: string; } // TODO this is incomplete and could be an array. export type ExportMapEntry = | string | { browser?: ExportMapEntry; import?: ExportMapEntry; default?: ExportMapEntry; require?: ExportMapEntry; }; export type ExportMap = Record; export type ExportField = string | ExportMap; // /** * https://github.com/defunctzombie/package-browser-field-spec * "browser": "main.js", * "browser": { "./": "main.js" } * "browser": { "./foo": false } // don't include in bundle */ export type BrowserField = string | Record; // This is the package.json, with fields we care about export type PackageManifest = { name: string; version: string; main?: string; // This is optional, actually module?: string; exports?: ExportField; browser?: BrowserField; types?: string; typings?: string; }; export type PackageManifestWithExports = PackageManifest & { exports: ExportMap; }; ================================================ FILE: esinstall/src/util.ts ================================================ import {promises as fs, realpathSync} from 'fs'; import path from 'path'; import validatePackageName from 'validate-npm-package-name'; import {InstallTarget, ImportMap, PackageManifest} from './types'; import resolve from 'resolve'; // We need to use eval here to prevent Rollup from detecting this use of `require()` export const NATIVE_REQUIRE = eval('require'); export async function writeLockfile(loc: string, importMap: ImportMap): Promise { const sortedImportMap: ImportMap = {imports: {}}; for (const key of Object.keys(importMap.imports).sort()) { sortedImportMap.imports[key] = importMap.imports[key]; } return fs.writeFile(loc, JSON.stringify(sortedImportMap, undefined, 2), {encoding: 'utf8'}); } export function isRemoteUrl(val: string): boolean { return /\w+\:\/\//.test(val) || val.startsWith('//'); } export function isTruthy(item: T | false | null | undefined): item is T { return Boolean(item); } /** Get the package name + an entrypoint within that package (if given). */ export function parsePackageImportSpecifier(imp: string): [string, string | null] { const impParts = imp.split('/'); if (imp.startsWith('@')) { const [scope, name, ...rest] = impParts; return [`${scope}/${name}`, rest.join('/') || null]; } const [name, ...rest] = impParts; return [name, rest.join('/') || null]; } /** * Given a package name, look for that package's package.json manifest. * Return both the manifest location (if believed to exist) and the * manifest itself (if found). * * NOTE: You used to be able to require() a package.json file directly, * but now with export map support in Node v13 that's no longer possible. */ export function resolveDependencyManifest( dep: string, cwd: string, ): [string | null, PackageManifest | null] { try { // resolve doesn't care about export map rules, so should find a package.json // if one does exist. const pkgPth = resolve.sync(`${dep}/package.json`, { basedir: cwd, }); const depManifest = realpathSync.native(pkgPth); return [depManifest, NATIVE_REQUIRE(depManifest)]; } catch { // This shouldn't ever happen if the package does exist. return [null, null]; } } /** * If Rollup erred parsing a particular file, show suggestions based on its * file extension (note: lowercase is fine). */ export const MISSING_PLUGIN_SUGGESTIONS: {[ext: string]: string} = { '.svelte': 'Try installing rollup-plugin-svelte and adding it to Snowpack (https://www.snowpack.dev/tutorials/svelte)', '.vue': 'Try installing rollup-plugin-vue and adding it to Snowpack (https://www.snowpack.dev/guides/vue)', }; /** * For the given import specifier, return an alias entry if one is matched. */ export function findMatchingAliasEntry( alias: Record, spec: string, ): {from: string; to: string; type: 'package' | 'path'} | undefined { // Only match bare module specifiers. relative and absolute imports should not match if ( spec === '.' || spec === '..' || spec.startsWith('./') || spec.startsWith('../') || spec.startsWith('/') || spec.startsWith('http://') || spec.startsWith('https://') ) { return undefined; } for (const [from, to] of Object.entries(alias)) { let foundType: 'package' | 'path' = isPackageAliasEntry(to) ? 'package' : 'path'; const isExactMatch = spec === removeTrailingSlash(from); const isDeepMatch = spec.startsWith(addTrailingSlash(from)); if (isExactMatch || isDeepMatch) { return { from, to, type: foundType, }; } } } /** * For the given import specifier, return an alias entry if one is matched. */ export function isPackageAliasEntry(val: string): boolean { return !path.isAbsolute(val); } /** Get full extensions of files */ export function getExt(fileName: string) { return { /** base extension (e.g. `.js`) */ baseExt: path.extname(fileName).toLocaleLowerCase(), /** full extension, if applicable (e.g. `.proxy.js`) */ expandedExt: path.basename(fileName).replace(/[^.]+/, '').toLocaleLowerCase(), }; } /** * Sanitizes npm packages that end in .js (e.g `tippy.js` -> `tippyjs`). * This is necessary because Snowpack can’t create both a file and directory * that end in .js. */ export function sanitizePackageName(filepath: string): string { const dirs = filepath.split('/'); const file = dirs.pop() as string; return [...dirs.map((path) => path.replace(/\.js$/i, 'js')), file].join('/'); } /** * Formats the snowpack dependency name from a "webDependencies" input value: * 2. Remove any ".js"/".mjs" extension (will be added automatically by Rollup) */ export function getWebDependencyName(dep: string): string { return validatePackageName(dep).validForNewPackages ? dep.replace(/\.js$/i, 'js') // if this is a top-level package ending in .js, replace with js (e.g. tippy.js -> tippyjs) : dep.replace(/\.m?js$/i, ''); // otherwise simply strip the extension (Rollup will resolve it) } /** Add / to beginning of string (but don’t double-up) */ export function addLeadingSlash(path: string) { return path.replace(/^\/?/, '/'); } /** Add / to the end of string (but don’t double-up) */ export function addTrailingSlash(path: string) { return path.replace(/\/?$/, '/'); } /** Remove \ and / from beginning of string */ export function removeLeadingSlash(path: string) { return path.replace(/^[/\\]+/, ''); } /** Remove \ and / from end of string */ export function removeTrailingSlash(path: string) { return path.replace(/[/\\]+$/, ''); } export function createInstallTarget(specifier: string, all = true): InstallTarget { return { specifier, all, default: false, namespace: false, named: [], }; } export function isJavaScript(pathname: string): boolean { const ext = path.extname(pathname).toLowerCase(); return ext === '.js' || ext === '.mjs' || ext === '.cjs'; } /** * Detect the web dependency "type" as either JS or ASSET: * - BUNDLE: Install and bundle this file with Rollup engine. * - ASSET: Copy this file directly. */ const bundleTypeExtensions = new Set(['.svelte', '.vue', '.astro']); export function getWebDependencyType(pathname: string): 'ASSET' | 'BUNDLE' | 'DTS' { const ext = path.extname(pathname).toLowerCase(); // JavaScript should always be bundled. if (isJavaScript(pathname)) { return 'BUNDLE'; } // Svelte & Vue (& Astro) should always be bundled because we want to show the missing plugin // error if a Svelte or Vue or Astro file is the install target. Without this, the .svelte/.vue // file would be treated like an asset and sent to the web as-is. if (bundleTypeExtensions.has(ext)) { return 'BUNDLE'; } // TypeScript typings if (pathname.endsWith('.d.ts')) { return 'DTS'; } // All other files should be treated as assets (copied over directly). return 'ASSET'; } ================================================ FILE: esinstall/tsconfig.json ================================================ { "include": ["src"], "compilerOptions": { "outDir": "lib", "module": "commonjs", "target": "es2018", "moduleResolution": "node", "declaration": true, "esModuleInterop": true, "strict": true, "sourceMap": true, "noImplicitAny": false, "noUnusedLocals": true, "noUnusedParameters": true, "skipLibCheck": true } } ================================================ FILE: examples/.gitignore ================================================ yarn.lock ================================================ FILE: examples/https-ssl-certificates/README.md ================================================ --- layout: ../../main.njk title: SSL Certificates --- ``` npm start -- --secure ``` Snowpack provides an easy way to use a local HTTPS server during development through the use of the `--secure` flag. When enabled, Snowpack will create an HTTPS server with HTTP2 support enabled using either: - (default) the `snowpack.key` and `snowpack.crt` file in the root directory of your site - (if provided) the TLS certificate and private key files at the paths specified in `devOptions.secure.cert` and `devOptions.secure.key` in the Snowpack configuration. ## Generating SSL Certificates You can automatically generate credentials for your project via either: - [devcert (no install required)](https://github.com/davewasmer/devcert-cli): `npx devcert-cli generate localhost` - [mkcert (install required)](https://github.com/FiloSottile/mkcert): `mkcert -install && mkcert -key-file snowpack.key -cert-file snowpack.crt localhost` In most situations you should add personally generated certificate files (`snowpack.key` and `snowpack.crt`) to your `.gitignore` file. ================================================ FILE: examples/https-ssl-certificates/index.html ================================================ Snowpack Example: HTTPS Local Devleopment Example: SSL Certificates

This site is using...

================================================ FILE: examples/https-ssl-certificates/package.json ================================================ { "dependencies": { "snowpack": "^3.0.0" } } ================================================ FILE: examples/https-ssl-certificates/snowpack.config.js ================================================ // Snowpack Configuration File // See all supported options: https://www.snowpack.dev/reference/configuration /** @type {import("snowpack").SnowpackUserConfig } */ module.exports = { mount: { /* ... */ }, plugins: [ /* ... */ ], packageOptions: { /* ... */ }, devOptions: { /* ... */ }, buildOptions: { /* ... */ }, }; ================================================ FILE: examples/react-global-imports/README.md ================================================ --- layout: ../../main.njk title: React + babel-plugin-import-global --- _Based on [app-template-react](../../create-snowpack-app/app-template-react)_ Example of using Snowpack in conjuction with [babel-plugin-import-global][babel-plugin-import-global]. This is useful when you need to need to inject an import statement at the top of every file, such as React: ```jsx // "import React from 'react'" no longer needed! function MyComponent() { // … } export default MyComponent; ``` To recreate this setup, follow 2 steps: 1. Create a [babel.config.js](./babel.config.js) file in the root of the project. Copy the settings shown. 2. Install [@snowpack/plugin-babel][snowpack-babel] and add it to [snowpack.config.mjs](./snowpack.config.js) ### ⚠️ Caveat When you use [@snowpack/plugin-babel][snowpack-babel], you miss out on the faster builds that come from Snowpack‘s default JS builder, [esbuild][esbuild] (we don‘t run both together to avoid conflict). However, if you skip Babel, you will have to manually place `import` statements yourself at the top of every file. We‘d recommend being explicit and manually managing every `import` statement yourself. You can simplify your setup, speed up your builds, and you might see benefits from being explicit. In order to do this, simply use our [React starter template][app-template-react]. No setup required. But if you‘ve weighed the tradeoffs and decide that a slower build is worth it to get global import functionality, then start from the example here. [app-template-react]: https://github.com/withastro/snowpack/create-snowpack-app/app-template-react [babel-plugin-import-global]: https://www.npmjs.com/package/babel-plugin-import-global [esbuild]: https://esbuild.github.io/ [snowpack-babel]: https://github.com/withastro/snowpack/plugins/plugin-babel ================================================ FILE: examples/react-global-imports/babel.config.js ================================================ module.exports = { presets: ['@babel/preset-react'], plugins: [ [ 'import-globals', { React: 'react', Component: {moduleName: 'react', exportName: 'Component'}, PropTypes: {moduleName: 'react', exportName: 'PropTypes'}, ReactDOM: 'react-dom', }, ], ], }; ================================================ FILE: examples/react-global-imports/package.json ================================================ { "name": "@snowpack/example-react-global-imports", "version": "0.0.1", "private": true, "scripts": { "start": "snowpack dev", "build": "snowpack build" }, "dependencies": { "react": "^17.0.0", "react-dom": "^17.0.0" }, "devDependencies": { "@babel/preset-react": "^7.12.7", "@snowpack/plugin-babel": "^2.1.4", "@snowpack/plugin-dotenv": "^2.0.4", "@snowpack/plugin-react-refresh": "^2.3.7", "babel-plugin-import-globals": "^2.0.0", "snowpack": "^3.0.0" } } ================================================ FILE: examples/react-global-imports/public/index.html ================================================ Snowpack App
================================================ FILE: examples/react-global-imports/public/robots.txt ================================================ # https://www.robotstxt.org/robotstxt.html User-agent: * Disallow: ================================================ FILE: examples/react-global-imports/snowpack.config.js ================================================ /** @type {import("snowpack").SnowpackUserConfig } */ module.exports = { mount: { public: '/', src: '/dist', }, plugins: ['@snowpack/plugin-babel', '@snowpack/plugin-react-refresh', '@snowpack/plugin-dotenv'], packageOptions: { knownEntrypoints: ['react', 'react-dom'], } }; ================================================ FILE: examples/react-global-imports/src/App.css ================================================ .App { text-align: center; } .App code { background: #FFF3; padding: 4px 8px; border-radius: 4px; } .App p { margin: 0.4rem; } .App-logo { height: 40vmin; pointer-events: none; } @media (prefers-reduced-motion: no-preference) { .App-logo { animation: App-logo-spin infinite 20s linear; } } .App-header { background-color: #282c34; min-height: 100vh; display: flex; flex-direction: column; align-items: center; justify-content: center; font-size: calc(10px + 2vmin); color: white; } .App-link { color: #61dafb; } @keyframes App-logo-spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } } ================================================ FILE: examples/react-global-imports/src/App.jsx ================================================ import {useState, useEffect} from 'react'; import logo from './logo.svg'; import './App.css'; function App() { // Create the count state. const [count, setCount] = useState(0); // Create the counter (+1 every second). useEffect(() => { const timer = setTimeout(() => setCount(count + 1), 1000); return () => clearTimeout(timer); }, [count, setCount]); // Return the App component. return (
logo

Edit src/App.jsx and save to reload.

Page has been open for {count} seconds.

Learn React

); } export default App; ================================================ FILE: examples/react-global-imports/src/index.css ================================================ body { margin: 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } code { font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace; } ================================================ FILE: examples/react-global-imports/src/index.jsx ================================================ import App from './App.jsx'; import './index.css'; ReactDOM.render( , document.getElementById('root'), ); // Hot Module Replacement (HMR) - Remove this snippet to remove HMR. // Learn more: https://www.snowpack.dev/concepts/hot-module-replacement if (import.meta.hot) { import.meta.hot.accept(); } ================================================ FILE: examples/react-loadable-components/README.md ================================================ --- layout: ../../main.njk title: React + Loadable Components --- _Based on [app-template-react](../../create-snowpack-app/app-template-react)_ You can lazy load React components in Snowpack when needed with React‘s builtin `React.lazy` ([docs][react-lazy]): ```jsx import React, {useState, useEffect, Suspense} from 'react'; const Async = React.lazy(() => import('./Async')); function Component() { return (
Loading...
}>
); } ``` This works out-of-the-box in Snowpack, with no configuration needed! ### Learn more - [`React.lazy` documentation on reactjs.org][react-lazy] [react-lazy]: https://reactjs.org/docs/code-splitting.html#reactlazy ================================================ FILE: examples/react-loadable-components/package.json ================================================ { "name": "@snowpack/example-react-loadable-components", "version": "0.0.1", "private": true, "scripts": { "start": "snowpack dev", "build": "snowpack build" }, "dependencies": { "react": "^17.0.0", "react-dom": "^17.0.0" }, "devDependencies": { "@snowpack/plugin-dotenv": "^2.0.4", "@snowpack/plugin-react-refresh": "^2.3.7", "snowpack": "^3.0.0" } } ================================================ FILE: examples/react-loadable-components/public/index.html ================================================ Snowpack App
================================================ FILE: examples/react-loadable-components/public/robots.txt ================================================ # https://www.robotstxt.org/robotstxt.html User-agent: * Disallow: ================================================ FILE: examples/react-loadable-components/snowpack.config.js ================================================ /** @type {import("snowpack").SnowpackUserConfig } */ module.exports = { mount: { public: '/', src: '/dist', }, plugins: ['@snowpack/plugin-react-refresh', '@snowpack/plugin-dotenv'], }; ================================================ FILE: examples/react-loadable-components/src/App.css ================================================ .App { text-align: center; } .App code { background: #FFF3; padding: 4px 8px; border-radius: 4px; } .App p { margin: 0.4rem; } .App-logo { height: 40vmin; pointer-events: none; } @media (prefers-reduced-motion: no-preference) { .App-logo { animation: App-logo-spin infinite 20s linear; } } .App-header { background-color: #282c34; min-height: 100vh; display: flex; flex-direction: column; align-items: center; justify-content: center; font-size: calc(10px + 2vmin); color: white; } .App-link { color: #61dafb; } @keyframes App-logo-spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } } ================================================ FILE: examples/react-loadable-components/src/App.jsx ================================================ import React, {useState, useEffect, Suspense} from 'react'; import logo from './logo.svg'; import './App.css'; const Async = React.lazy(() => import('./Async')); function App() { // Create the count state. const [count, setCount] = useState(0); // Create the counter (+1 every second). useEffect(() => { const timer = setTimeout(() => setCount(count + 1), 1000); return () => clearTimeout(timer); }, [count, setCount]); // Return the App component. return (
logo

Edit src/App.jsx and save to reload.

Page has been open for {count} seconds.

Learn React

Loading...
}>
); } export default App; ================================================ FILE: examples/react-loadable-components/src/Async.jsx ================================================ import React from 'react'; function Async() { return
I‘m an Async Component!
; } export default Async; ================================================ FILE: examples/react-loadable-components/src/index.css ================================================ body { margin: 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } code { font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace; } ================================================ FILE: examples/react-loadable-components/src/index.jsx ================================================ import React from 'react'; import ReactDOM from 'react-dom'; import App from './App.jsx'; import './index.css'; ReactDOM.render( , document.getElementById('root'), ); // Hot Module Replacement (HMR) - Remove this snippet to remove HMR. // Learn more: https://www.snowpack.dev/concepts/hot-module-replacement if (import.meta.hot) { import.meta.hot.accept(); } ================================================ FILE: examples/tailwind/README.md ================================================ --- layout: ../../main.njk title: Tailwind --- ### Learn more - [Tailwind docs on Snowpack][tailwind] [tailwind]: https://www.snowpack.dev/guides/tailwind-css/ ================================================ FILE: examples/tailwind/package.json ================================================ { "name": "@snowpack/example-react-loadable-components", "version": "0.0.1", "private": true, "scripts": { "start": "snowpack dev", "build": "snowpack build" }, "devDependencies": { "@snowpack/plugin-postcss": "^1.3.0", "postcss": "^8.3.5", "snowpack": "^3.4.0", "tailwindcss": "^2.1.2" } } ================================================ FILE: examples/tailwind/postcss.config.js ================================================ module.exports = { plugins: { tailwindcss: {}, // other plugins can go here, such as autoprefixer }, }; ================================================ FILE: examples/tailwind/public/global.css ================================================ @tailwind base; @tailwind components; @tailwind utilities; ================================================ FILE: examples/tailwind/public/index.html ================================================ Tailwind Example

“Tailwind CSS is the only framework that I've seen scale on large teams. It’s easy to customize, adapts to any design, and the build size is tiny.”

Sarah Dayan
Staff Engineer, Algolia
================================================ FILE: examples/tailwind/snowpack.config.mjs ================================================ /** @type {import("snowpack").SnowpackUserConfig } */ export default { mount: { public: '/', src: '/_dist', }, devOptions: { tailwindConfig: './tailwind.config.js', }, plugins: ['@snowpack/plugin-postcss'], }; ================================================ FILE: examples/tailwind/tailwind.config.js ================================================ module.exports = { mode: 'jit', purge: ['./public/**/*.html', './src/**/*.{js,jsx,ts,tsx,vue}'], // specify other options here }; ================================================ FILE: jest.config.js ================================================ module.exports = { modulePathIgnorePatterns: [ '/create-snowpack-app/app-template-', // don’t run tests intended as user examples '/examples', // don’t run any tests in examples '/test/create-snowpack-app/test-install', // don’t run tests inside our mock create-snowpack-app install '/www', // docs has its own tests ], globalSetup: '/jest.setup.js', testEnvironment: 'node', }; ================================================ FILE: jest.setup.js ================================================ module.exports = async () => { // enable NO_COLOR mode for Jest process.env.NO_COLOR = true; // Clear the temp directory const path = require('path'); const rimraf = require('rimraf'); const fs = require('fs'); rimraf.sync(path.join(__dirname, 'test', '__temp__')); fs.mkdirSync(path.join(__dirname, 'test', '__temp__')); }; ================================================ FILE: lerna.json ================================================ { "version": "independent", "packages": ["snowpack", "skypack", "esinstall", "create-snowpack-app/*", "plugins/*"], "npmClient": "yarn", "useWorkspaces": true } ================================================ FILE: package.json ================================================ { "name": "root", "private": true, "scripts": { "bootstrap": "lerna bootstrap", "build": "lerna run build --scope=esinstall --scope=snowpack --scope=skypack", "build:watch": "lerna run build:watch --parallel --scope=esinstall --scope=snowpack --scope=skypack", "build:docs": "cd www && yarn build", "publish": "npm run build && lerna publish --no-private", "lint": "lerna run lint --parallel --scope=esinstall --scope=snowpack --scope=skypack", "format": "prettier --write '{snowpack,skypack,esinstall}/src/**/*.{ts,js,json}' '{scripts,test,plugins,create-snowpack-app}/**/*.{ts,js,json}' '{.dependabot,.github,www,docs}/**/*.{md,yml,js}'", "test": "jest --testPathIgnorePatterns=/test-dev/ --test-timeout=30000", "test:dev": "jest /test-dev/ --test-timeout=30000", "test:docs": "cd www && yarn && yarn test --passWithNoTests" }, "workspaces": [ "esinstall", "esm-runtime", "snowpack", "skypack", "create-snowpack-app/*", "plugins/*", "test/build/*", "test/esinstall/*" ], "devDependencies": { "@changesets/cli": "^2.16.0", "@rollup/plugin-alias": "^3.0.1", "@skypack/package-check": "^0.2.0", "@types/babel__traverse": "^7.0.7", "@types/cacache": "^12.0.1", "@types/compressible": "^2.0.0", "@types/css-modules-loader-core": "^1.1.0", "@types/detect-port": "^1.3.0", "@types/es-module-lexer": "^0.3.0", "@types/etag": "^1.8.0", "@types/http-proxy": "^1.17.4", "@types/mime-types": "^2.1.0", "@types/mkdirp": "^1.0.0", "@types/node": "^14.14.14", "@types/rimraf": "^3.0.0", "@types/signal-exit": "^3.0.0", "@types/validate-npm-package-name": "^3.0.0", "@types/ws": "^7.2.4", "cheerio": "^1.0.0-rc.10", "cross-env": "^7.0.2", "cssnano": "^5.0.6", "dedent": "^0.7.0", "execa": "^5.1.1", "httpie": "^1.1.2", "jest": "^27.0.5", "jest-specific-snapshot": "^4.0.0", "lerna": "^3.22.1", "prettier": "^2.3.1", "strip-ansi": "^6.0.0", "typescript": "^4.3.4" }, "prettier": { "singleQuote": true, "trailingComma": "all", "bracketSpacing": false, "printWidth": 100 } } ================================================ FILE: plugins/plugin-babel/CHANGELOG.md ================================================ # @snowpack/plugin-babel ## 2.1.7 ### Patch Changes - b34b011a: update babel plugin to support process.env packages - ecd6ce07: Fix undefined accessor errors using @snowpack/plugin-babel with snowpack 3.1.x (#3000) - 48ced185: Update plugin-babel readme (#2574) _For older releases, check our curated [release update thread](https://github.com/withastro/snowpack/discussions/1183) or the raw [commit history](https://github.com/withastro/snowpack/commits/main/plugins/plugin-babel)._ ================================================ FILE: plugins/plugin-babel/README.md ================================================ # @snowpack/plugin-babel Use Babel to build your files from source. Automatically inherits from your local project `.babelrc` or `babel.config.json` files. ``` npm install --save-dev @snowpack/plugin-babel ``` ```js // snowpack.config.mjs export default { plugins: [ [ '@snowpack/plugin-babel', { input: ['.js', '.mjs', '.jsx', '.ts', '.tsx'], // (optional) specify files for Babel to transform transformOptions: { // babel transform options }, }, ], ], }; ``` #### Plugin Options | Name | Type | Description | | :----------------- | :--------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------- | | `input` | `string[]` | (optional) By default, Babel scans & transfoms these extensions: `['.js', '.mjs', '.jsx', '.ts', '.tsx']`. Modify this array if you’d like to change this. | | `transformOptions` | `object` | (optional) See [https://babeljs.io/docs/en/options](https://babeljs.io/docs/en/options) | ================================================ FILE: plugins/plugin-babel/package.json ================================================ { "name": "@snowpack/plugin-babel", "version": "2.1.7", "main": "plugin.js", "license": "MIT", "homepage": "https://github.com/withastro/snowpack/tree/main/plugins/plugin-babel#readme", "repository": { "type": "git", "url": "https://github.com/withastro/snowpack.git", "directory": "plugins/plugin-babel" }, "publishConfig": { "access": "public" }, "dependencies": { "@babel/core": "^7.14.0", "workerpool": "^6.0.0" }, "gitHead": "a01616bb0787d56cd782f94cecf2daa12c7594e4" } ================================================ FILE: plugins/plugin-babel/plugin.js ================================================ const workerpool = require('workerpool'); let worker, pool; module.exports = function plugin(snowpackConfig, options = {}) { // options validation if (options) { if (typeof options !== 'object') throw new Error(`options isn’t an object. Please see README.`); if (options.input && !Array.isArray(options.input)) throw new Error( `options.input must be an array (e.g. ['.js', '.mjs', '.jsx', '.ts', '.tsx'])`, ); if (options.input && !options.input.length) throw new Error(`options.input must specify at least one filetype`); } return { name: '@snowpack/plugin-babel', resolve: { input: options.input || ['.js', '.mjs', '.jsx', '.ts', '.tsx'], output: ['.js'], // always export JS }, async load({filePath, isPackage}) { if (!filePath) { return; } pool = pool || workerpool.pool(require.resolve('./worker.js')); worker = worker || (await pool.proxy()); let encodedResult = await worker.transformFileAsync(filePath, { caller: { name: '@snowpack/plugin-babel', supportsStaticESM: true, supportsDynamicImport: true, supportsTopLevelAwait: true, supportsExportNamespaceFrom: true, }, cwd: snowpackConfig.root || process.cwd(), ast: false, compact: false, sourceMaps: snowpackConfig.buildOptions.sourcemap || snowpackConfig.buildOptions.sourceMaps, ...(options.transformOptions || {}), }); let {code, map} = JSON.parse(encodedResult); if (code) { // Some Babel plugins assume process.env exists, but Snowpack // uses import.meta.env instead. Handle this here since it // seems to be pretty common. // See: https://www.pika.dev/npm/snowpack/discuss/496 if (!isPackage) { code = code.replace(/process\.env/g, 'import.meta.env'); } } return { '.js': { code, map, }, }; }, cleanup() { pool && pool.terminate(); }, }; }; ================================================ FILE: plugins/plugin-babel/test/plugin-babel.test.js ================================================ const plugin = require('../plugin.js'); jest.mock('@babel/core'); const babel = require('@babel/core'); babel.transformFileAsync = jest.fn(() => Promise.resolve({code: 'code', map: 'map'})); /** jest mock above in worker will not work */ jest.mock('workerpool'); const workerpool = require('workerpool'); workerpool.pool = jest.fn((path) => ({ proxy: jest.fn(() => Promise.resolve({ transformFileAsync: (...args) => babel.transformFileAsync(...args).then(JSON.stringify), }), ), })); beforeEach(() => { babel.transformFileAsync.mockClear(); }); describe('plugin-babel', () => { test('no options', async () => { const p = plugin( { buildOptions: {}, }, {}, ); expect(p).toMatchInlineSnapshot(` Object { "cleanup": [Function], "load": [Function], "name": "@snowpack/plugin-babel", "resolve": Object { "input": Array [ ".js", ".mjs", ".jsx", ".ts", ".tsx", ], "output": Array [ ".js", ], }, } `); const result = await p.load({ filePath: 'test.js', }); const [filePath, options] = babel.transformFileAsync.mock.calls[0]; expect(filePath).toMatchInlineSnapshot(`"test.js"`); expect(options).toMatchObject({ cwd: process.cwd(), ast: false, compact: false, sourceMaps: undefined, }); expect(result).toMatchInlineSnapshot(` Object { ".js": Object { "code": "code", "map": "map", }, } `); }); describe('input option', () => { test('input option must be an array has at least 1 value', () => { expect(() => plugin( { buildOptions: {}, }, { input: '.js', }, ), ).toThrowErrorMatchingInlineSnapshot( `"options.input must be an array (e.g. ['.js', '.mjs', '.jsx', '.ts', '.tsx'])"`, ); expect(() => plugin( { buildOptions: {}, }, { input: [], }, ), ).toThrowErrorMatchingInlineSnapshot(`"options.input must specify at least one filetype"`); }); test('input option will overwrite the default resolve config', () => { expect( plugin( { buildOptions: {}, }, { input: ['.js'], }, ).resolve, ).toMatchInlineSnapshot(` Object { "input": Array [ ".js", ], "output": Array [ ".js", ], } `); }); }); test('has transformOptions', async () => { const transformOptions = { ast: true, plugins: ['jsx'], }; const p = plugin( {buildOptions: {}}, { transformOptions, }, ); await p.load({ filePath: 'test.js', }); const [filePath, options] = babel.transformFileAsync.mock.calls[0]; expect(options).toMatchObject({ cwd: process.cwd(), ast: false, compact: false, sourceMaps: undefined, ...transformOptions, }); }); test('sourceMaps option will be overwritten', async () => { const transformOptions = { sourceMaps: 'inline', }; const p = plugin( { buildOptions: { sourceMaps: false, }, }, { transformOptions, }, ); await p.load({ filePath: 'test.js', }); const [filePath, options] = babel.transformFileAsync.mock.calls[0]; expect(options).toMatchObject({ cwd: process.cwd(), ast: false, compact: false, ...transformOptions, }); }); test('process.env will be converted for source files', async () => { // Modify transformFileAsync mock to include a dummy `process.env` babel.transformFileAsync = jest.fn(() => Promise.resolve({code: 'code [process.env.test]', map: 'map'}), ); const p = plugin({ buildOptions: {}, }); const result = await p.load({ filePath: 'test.js', isPackage: false, // testing a source file }); // Expect process.env to be converted to import.meta.env expect(result).toMatchInlineSnapshot(` Object { ".js": Object { "code": "code [import.meta.env.test]", "map": "map", }, } `); }); test('process.env will not be touched for package files', async () => { // Modify transformFileAsync mock to include a dummy `process.env` babel.transformFileAsync = jest.fn(() => Promise.resolve({code: 'code [process.env.test]', map: 'map'}), ); const p = plugin({ buildOptions: {}, }); const result = await p.load({ filePath: 'test.js', isPackage: true, // testing a package file }); // Expect output to include import.meta.env default snippet expect(result).toMatchInlineSnapshot(` Object { ".js": Object { "code": "code [process.env.test]", "map": "map", }, } `); }); }); ================================================ FILE: plugins/plugin-babel/worker.js ================================================ const workerpool = require('workerpool'); const babel = require('@babel/core'); async function transformFileAsync(path, options) { const {code, map} = await babel.transformFileAsync(path, options); return JSON.stringify({code, map}); } // create a worker and register public functions workerpool.worker({ transformFileAsync, }); ================================================ FILE: plugins/plugin-build-script/README.md ================================================ # @snowpack/plugin-build-script A Snowpack plugin to build files in your application using any CLI tool. This plugin passes matching files as input to a custom CLI command and returns the output response as the build result. This is useful for connecting a custom CLI or when no Snowpack plugin exists for a favorite build tool. Note: All Snowpack < v2.6 `build:*` scripts now use this plugin behind the scenes. Usage: ```bash npm install @snowpack/plugin-build-script ``` Then add the plugin to your Snowpack config: ```js // snowpack.config.mjs export default { plugins: [ [ '@snowpack/plugin-build-script', { input: ['.tsx'], // files to watch output: ['.tsx'], // files to export cmd: 'babel --filename $FILE', // cmd to run }, ], ], }; ``` ## Plugin Options | Name | Type | Description | | :------- | :--------: | :-------------------------------------------------------------------------- | | `input` | `string[]` | Array of extensions to watch for. | | `output` | `string[]` | Array of extensions this plugin will output. | | `cmd` | `string` | Command to run on every file matching `input`. Accepts the `$FILE` env var. | ================================================ FILE: plugins/plugin-build-script/package.json ================================================ { "version": "2.1.0", "name": "@snowpack/plugin-build-script", "main": "plugin.js", "license": "MIT", "homepage": "https://github.com/withastro/snowpack/tree/main/plugins/plugin-build-script#readme", "repository": { "type": "git", "url": "https://github.com/withastro/snowpack.git", "directory": "plugins/plugin-build-script" }, "publishConfig": { "access": "public" }, "dependencies": { "execa": "^5.1.1", "npm-run-path": "^4.0.1" } } ================================================ FILE: plugins/plugin-build-script/plugin.js ================================================ const execa = require('execa'); const npmRunPath = require('npm-run-path'); const {promises: fs} = require('fs'); function buildScriptPlugin(snowpackConfig, {input, output, cmd}) { if (output.length !== 1) { throw new Error('Requires one output.'); } return { name: `build:${cmd.split(' ')[0]}`, resolve: { input: input, output: output, }, async load({filePath}) { const cmdWithFile = cmd.replace('$FILE', filePath); const contents = await fs.readFile(filePath, 'utf-8'); const {stdout, stderr, exitCode} = await execa.command(cmdWithFile, { env: npmRunPath.env(), extendEnv: true, shell: true, windowsHide: false, input: contents, cwd: snowpackConfig.root || process.cwd(), }); // If the command failed, fail the plugin as well. if (exitCode !== 0) { throw new Error(stderr || stdout); } // If the plugin outputs to stderr, show it to the user. if (stderr) { console.warn(stderr); } return {[output[0]]: stdout}; }, }; } module.exports = buildScriptPlugin; ================================================ FILE: plugins/plugin-build-script/test/plugin.test.js ================================================ const fs = require('fs').promises; const execa = require('execa'); const plugin = require('../plugin'); describe('@snowpack/plugin-build-script', () => { beforeEach(() => { const execaResult = { stdout: 'stdout', stderr: '', exitCode: 0, // Execa is weird, and returns a promise that also has other properties. Fake that here. catch: () => { return execaResult; }, }; execa.command = jest.fn().mockName('execa.command').mockReturnValue(execaResult); fs.readFile = jest.fn().mockResolvedValue('content'); }); test(`README example`, async () => { const {load} = plugin( {}, { input: ['.tsx'], // files to watch output: ['.tsx'], // files to export cmd: 'babel --filename $FILE', // cmd to run }, ); const result = await load({ filePath: 'path/to/file.tsx', }); expect(execa.command.mock.calls.length).toEqual(1); expect(execa.command.mock.calls[0][0]).toEqual('babel --filename path/to/file.tsx'); expect(result).toStrictEqual({'.tsx': 'stdout'}); }); }); ================================================ FILE: plugins/plugin-dotenv/CHANGELOG.md ================================================ # @snowpack/plugin-dotenv ## 2.2.0 ### Minor Changes - cf1bd442: Added an option to disable dotenv-expand functionality ## 2.1.0 ### Minor Changes - 38031e7e: Add options to plugin-dotenv (#2917) ### Patch Changes - ad8de420: [ci] yarn format - ae4c04bd: Updated link; reference page had moved (#2646) _For older releases, check our curated [release update thread](https://github.com/withastro/snowpack/discussions/1183) or the raw [commit history](https://github.com/withastro/snowpack/commits/main/plugins/plugin-dotenv)._ ================================================ FILE: plugins/plugin-dotenv/README.md ================================================ # @snowpack/plugin-dotenv Use [dotenv](https://github.com/motdotla/dotenv) to load environment variables from your project `.env` files. See Snowpack's [Environment Variables](https://www.snowpack.dev/reference/environment-variables) documentation to learn more. ``` npm install --save-dev @snowpack/plugin-dotenv ``` ```js // snowpack.config.mjs export default { plugins: ['@snowpack/plugin-dotenv'], }; ``` ``` # .env SNOWPACK_PUBLIC_ENABLE_FEATURE=true ``` **NOTE:** Snowpack requires the `SNOWPACK_PUBLIC_` prefix to recognize environment variables. This is to prevent accidental exposure of keys and secrets. #### What is Supported? - [dotenv-expand](https://github.com/motdotla/dotenv-expand) - `.env.NODE_ENV.local` - `.env.local` - `.env.NODE_ENV` - `.env` #### Plugin Options | Name | Type | Description | | :------- | :-------- | :------------------------------------------------------------------------------------------------- | | `dir` | `string` | (optional) Where to find `.env` files. Default is your current working directory (`process.cwd()`) | | `expand` | `boolean` | (optional) Enable `dotenv-expand` support. Default is `true` | ================================================ FILE: plugins/plugin-dotenv/package.json ================================================ { "name": "@snowpack/plugin-dotenv", "version": "2.2.0", "main": "plugin.js", "license": "MIT", "homepage": "https://github.com/withastro/snowpack/tree/main/plugins/plugin-dotenv#readme", "repository": { "type": "git", "url": "https://github.com/withastro/snowpack.git", "directory": "plugins/plugin-dotenv" }, "publishConfig": { "access": "public" }, "dependencies": { "dotenv": "^8.2.0", "dotenv-expand": "^5.1.0" } } ================================================ FILE: plugins/plugin-dotenv/plugin.js ================================================ const fs = require('fs'); const path = require('path'); module.exports = function plugin(snowpackConfig, options) { const NODE_ENV = process.env.NODE_ENV; // https://github.com/bkeepers/dotenv#what-other-env-files-can-i-use const dotenvFiles = [ NODE_ENV && `.env.${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' && `.env.local`, NODE_ENV && `.env.${NODE_ENV}`, '.env', ].filter(Boolean); const dir = options && options.dir ? options.dir.toString() : '.'; const expand = options && options.expand !== undefined ? options.expand : true; // 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) => { dotenvFile = path.resolve(process.cwd(), dir, dotenvFile); if (fs.existsSync(dotenvFile)) { const dotenv = require('dotenv').config({ path: dotenvFile, }); if (expand) require('dotenv-expand')(dotenv); } }); return { name: '@snowpack/plugin-dotenv', }; }; ================================================ FILE: plugins/plugin-dotenv/test/__snapshots__/plugin.test.js.snap ================================================ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`NODE_ENV= development 1`] = ` Object { "__DOTENV_DEVELOPMENT": "DEVELOPMENT", "__DOTENV_DEVELOPMENT_LOCAL": "DEVELOPMENT_LOCAL", "__DOTENV_ENV": "ENV", "__DOTENV_EXPAND": "PRESET", "__DOTENV_LOCAL": "LOCAL", "__DOTENV_PRESET": "PRESET", "__DOTENV_PRODUCTION": "ENV", "__DOTENV_PRODUCTION_LOCAL": "LOCAL", "__DOTENV_TEST": "ENV", "__DOTENV_TEST_LOCAL": "LOCAL", } `; exports[`NODE_ENV= production 1`] = ` Object { "__DOTENV_DEVELOPMENT": "ENV", "__DOTENV_DEVELOPMENT_LOCAL": "LOCAL", "__DOTENV_ENV": "ENV", "__DOTENV_EXPAND": "PRESET", "__DOTENV_LOCAL": "LOCAL", "__DOTENV_PRESET": "PRESET", "__DOTENV_PRODUCTION": "PRODUCTION", "__DOTENV_PRODUCTION_LOCAL": "PRODUCTION_LOCAL", "__DOTENV_TEST": "ENV", "__DOTENV_TEST_LOCAL": "LOCAL", } `; exports[`NODE_ENV= test 1`] = ` Object { "__DOTENV_DEVELOPMENT": "ENV", "__DOTENV_DEVELOPMENT_LOCAL": "ENV", "__DOTENV_ENV": "ENV", "__DOTENV_EXPAND": "PRESET", "__DOTENV_LOCAL": "TEST", "__DOTENV_PRESET": "PRESET", "__DOTENV_PRODUCTION": "ENV", "__DOTENV_PRODUCTION_LOCAL": "ENV", "__DOTENV_TEST": "TEST", "__DOTENV_TEST_LOCAL": "TEST_LOCAL", } `; exports[`NODE_ENV= undefined 1`] = ` Object { "__DOTENV_DEVELOPMENT": "ENV", "__DOTENV_DEVELOPMENT_LOCAL": "LOCAL", "__DOTENV_ENV": "ENV", "__DOTENV_EXPAND": "PRESET", "__DOTENV_LOCAL": "LOCAL", "__DOTENV_PRESET": "PRESET", "__DOTENV_PRODUCTION": "ENV", "__DOTENV_PRODUCTION_LOCAL": "LOCAL", "__DOTENV_TEST": "ENV", "__DOTENV_TEST_LOCAL": "LOCAL", } `; exports[`with dir NODE_ENV= development 1`] = ` Object { "__DOTENV_DEVELOPMENT": "DEVELOPMENT", "__DOTENV_DEVELOPMENT_LOCAL": "DEVELOPMENT_LOCAL", "__DOTENV_ENV": "ENV", "__DOTENV_LOCAL": "LOCAL", "__DOTENV_PRESET": "PRESET", "__DOTENV_PRODUCTION": "ENV", "__DOTENV_PRODUCTION_LOCAL": "LOCAL", "__DOTENV_TEST": "ENV", "__DOTENV_TEST_LOCAL": "LOCAL", } `; exports[`with dir NODE_ENV= production 1`] = ` Object { "__DOTENV_DEVELOPMENT": "ENV", "__DOTENV_DEVELOPMENT_LOCAL": "LOCAL", "__DOTENV_ENV": "ENV", "__DOTENV_LOCAL": "LOCAL", "__DOTENV_PRESET": "PRESET", "__DOTENV_PRODUCTION": "PRODUCTION", "__DOTENV_PRODUCTION_LOCAL": "PRODUCTION_LOCAL", "__DOTENV_TEST": "ENV", "__DOTENV_TEST_LOCAL": "LOCAL", } `; exports[`with dir NODE_ENV= test 1`] = ` Object { "__DOTENV_DEVELOPMENT": "ENV", "__DOTENV_DEVELOPMENT_LOCAL": "ENV", "__DOTENV_ENV": "ENV", "__DOTENV_LOCAL": "TEST", "__DOTENV_PRESET": "PRESET", "__DOTENV_PRODUCTION": "ENV", "__DOTENV_PRODUCTION_LOCAL": "ENV", "__DOTENV_TEST": "TEST", "__DOTENV_TEST_LOCAL": "TEST_LOCAL", } `; exports[`with dir NODE_ENV= undefined 1`] = ` Object { "__DOTENV_DEVELOPMENT": "ENV", "__DOTENV_DEVELOPMENT_LOCAL": "LOCAL", "__DOTENV_ENV": "ENV", "__DOTENV_LOCAL": "LOCAL", "__DOTENV_PRESET": "PRESET", "__DOTENV_PRODUCTION": "ENV", "__DOTENV_PRODUCTION_LOCAL": "LOCAL", "__DOTENV_TEST": "ENV", "__DOTENV_TEST_LOCAL": "LOCAL", } `; exports[`with expand explicitly off development 1`] = ` Object { "__DOTENV_DEVELOPMENT": "DEVELOPMENT", "__DOTENV_DEVELOPMENT_LOCAL": "DEVELOPMENT_LOCAL", "__DOTENV_ENV": "ENV", "__DOTENV_EXPAND": "\${__DOTENV_PRESET}", "__DOTENV_LOCAL": "LOCAL", "__DOTENV_PRESET": "PRESET", "__DOTENV_PRODUCTION": "ENV", "__DOTENV_PRODUCTION_LOCAL": "LOCAL", "__DOTENV_TEST": "ENV", "__DOTENV_TEST_LOCAL": "LOCAL", } `; exports[`with expand explicitly off production 1`] = ` Object { "__DOTENV_DEVELOPMENT": "ENV", "__DOTENV_DEVELOPMENT_LOCAL": "LOCAL", "__DOTENV_ENV": "ENV", "__DOTENV_EXPAND": "\${__DOTENV_PRESET}", "__DOTENV_LOCAL": "LOCAL", "__DOTENV_PRESET": "PRESET", "__DOTENV_PRODUCTION": "PRODUCTION", "__DOTENV_PRODUCTION_LOCAL": "PRODUCTION_LOCAL", "__DOTENV_TEST": "ENV", "__DOTENV_TEST_LOCAL": "LOCAL", } `; exports[`with expand explicitly off test 1`] = ` Object { "__DOTENV_DEVELOPMENT": "ENV", "__DOTENV_DEVELOPMENT_LOCAL": "ENV", "__DOTENV_ENV": "ENV", "__DOTENV_EXPAND": "\${__DOTENV_PRESET}", "__DOTENV_LOCAL": "TEST", "__DOTENV_PRESET": "PRESET", "__DOTENV_PRODUCTION": "ENV", "__DOTENV_PRODUCTION_LOCAL": "ENV", "__DOTENV_TEST": "TEST", "__DOTENV_TEST_LOCAL": "TEST_LOCAL", } `; exports[`with expand explicitly off undefined 1`] = ` Object { "__DOTENV_DEVELOPMENT": "ENV", "__DOTENV_DEVELOPMENT_LOCAL": "LOCAL", "__DOTENV_ENV": "ENV", "__DOTENV_EXPAND": "\${__DOTENV_PRESET}", "__DOTENV_LOCAL": "LOCAL", "__DOTENV_PRESET": "PRESET", "__DOTENV_PRODUCTION": "ENV", "__DOTENV_PRODUCTION_LOCAL": "LOCAL", "__DOTENV_TEST": "ENV", "__DOTENV_TEST_LOCAL": "LOCAL", } `; exports[`with expand explicitly on development 1`] = ` Object { "__DOTENV_DEVELOPMENT": "DEVELOPMENT", "__DOTENV_DEVELOPMENT_LOCAL": "DEVELOPMENT_LOCAL", "__DOTENV_ENV": "ENV", "__DOTENV_EXPAND": "PRESET", "__DOTENV_LOCAL": "LOCAL", "__DOTENV_PRESET": "PRESET", "__DOTENV_PRODUCTION": "ENV", "__DOTENV_PRODUCTION_LOCAL": "LOCAL", "__DOTENV_TEST": "ENV", "__DOTENV_TEST_LOCAL": "LOCAL", } `; exports[`with expand explicitly on production 1`] = ` Object { "__DOTENV_DEVELOPMENT": "ENV", "__DOTENV_DEVELOPMENT_LOCAL": "LOCAL", "__DOTENV_ENV": "ENV", "__DOTENV_EXPAND": "PRESET", "__DOTENV_LOCAL": "LOCAL", "__DOTENV_PRESET": "PRESET", "__DOTENV_PRODUCTION": "PRODUCTION", "__DOTENV_PRODUCTION_LOCAL": "PRODUCTION_LOCAL", "__DOTENV_TEST": "ENV", "__DOTENV_TEST_LOCAL": "LOCAL", } `; exports[`with expand explicitly on test 1`] = ` Object { "__DOTENV_DEVELOPMENT": "ENV", "__DOTENV_DEVELOPMENT_LOCAL": "ENV", "__DOTENV_ENV": "ENV", "__DOTENV_EXPAND": "PRESET", "__DOTENV_LOCAL": "TEST", "__DOTENV_PRESET": "PRESET", "__DOTENV_PRODUCTION": "ENV", "__DOTENV_PRODUCTION_LOCAL": "ENV", "__DOTENV_TEST": "TEST", "__DOTENV_TEST_LOCAL": "TEST_LOCAL", } `; exports[`with expand explicitly on undefined 1`] = ` Object { "__DOTENV_DEVELOPMENT": "ENV", "__DOTENV_DEVELOPMENT_LOCAL": "LOCAL", "__DOTENV_ENV": "ENV", "__DOTENV_EXPAND": "PRESET", "__DOTENV_LOCAL": "LOCAL", "__DOTENV_PRESET": "PRESET", "__DOTENV_PRODUCTION": "ENV", "__DOTENV_PRODUCTION_LOCAL": "LOCAL", "__DOTENV_TEST": "ENV", "__DOTENV_TEST_LOCAL": "LOCAL", } `; ================================================ FILE: plugins/plugin-dotenv/test/execPlugin.js ================================================ const dotenvPlugin = require('../plugin'); const params = JSON.parse(process.argv[2]); dotenvPlugin(null, params); const testEnvs = Object.keys(process.env) .filter((key) => key.startsWith('__DOTENV_')) .reduce((ret, curr) => { ret[curr] = process.env[curr]; return ret; }, {}); console.log(JSON.stringify(testEnvs)); ================================================ FILE: plugins/plugin-dotenv/test/plugin.test.js ================================================ const path = require('path'); const execa = require('execa'); const execPluginFilePath = require.resolve('./execPlugin'); const testWorkDirectory = path.join(__dirname, 'env'); function execPlugin(nodeEnv, params = null) { const {stdout} = execa.sync('node', [execPluginFilePath, JSON.stringify(params)], { cwd: testWorkDirectory, env: {NODE_ENV: nodeEnv}, }); return JSON.parse(stdout); } beforeEach(() => { // test that pre-exiting environment variables are not overwritten process.env.__DOTENV_PRESET = 'PRESET'; }); const NODE_ENV_LIST = [undefined, 'development', 'test', 'production']; describe('NODE_ENV=', () => { NODE_ENV_LIST.forEach((nodeEnv) => { test(`${nodeEnv}`, () => { expect(execPlugin(nodeEnv)).toMatchSnapshot(); }); }); }); describe('with dir NODE_ENV=', () => { NODE_ENV_LIST.forEach((nodeEnv) => { test(`${nodeEnv}`, () => { expect(execPlugin(nodeEnv, {dir: 'subdir'})).toMatchSnapshot(); }); }); }); describe('with expand explicitly off', () => { NODE_ENV_LIST.forEach((nodeEnv) => { test(`${nodeEnv}`, () => { expect(execPlugin(nodeEnv, {expand: false})).toMatchSnapshot(); }); }); }); describe('with expand explicitly on', () => { NODE_ENV_LIST.forEach((nodeEnv) => { test(`${nodeEnv}`, () => { expect(execPlugin(nodeEnv, {expand: true})).toMatchSnapshot(); }); }); }); ================================================ FILE: plugins/plugin-optimize/CHANGELOG.md ================================================ # @snowpack/plugin-optimize ## 0.2.13 ### Patch Changes - 63f0d8d7: add mkdirp to plugin-optimize - a65023e2: [ci] yarn format ## 0.2.12 ### Patch Changes - 3eca3476: [ci] yarn format - 916b18a0: mark plugin-optimize as no longer private ## 0.2.11 ### Patch Changes - 03bba7e6: update post - ee9e90e7: skip plugin optimize tests - d45e91ca: Fix deprecated option causing tests to fail (#2258) - c0735b36: fix: deprecated config in @snowpack/plugin-optimize (#2251) - 9515ef50: make optimize plugin private - 1c2a2094: update lockfile - e2c2d5f4: final cleanup, tests passing _For older releases, check our curated [release update thread](https://github.com/withastro/snowpack/discussions/1183) or the raw [commit history](https://github.com/withastro/snowpack/commits/main/plugins/plugin-optimize)._ ================================================ FILE: plugins/plugin-optimize/README.md ================================================ # @snowpack/plugin-optimize Optimize your unbundled Snowpack app: - ✅ Minify HTML - ✅ Minify CSS - ✅ Minify JS - ✅ Transpile JS - ✅ [Preload JS Modules][https://developers.google.com/web/updates/2017/12/modulepreload] ### Usage From a terminal, run the following: ``` npm install --save-dev @snowpack/plugin-optimize ``` Then add this plugin to your Snowpack config: ```js // snowpack.config.mjs export default { plugins: [ [ '@snowpack/plugin-optimize', { /* see options below */ }, ], ], }; ``` ### Plugin Options | Name | Type | Description | | :------------------- | :---------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `minifyJS` | `boolean` | Enable JS minification (default: `true`) | | `minifyCSS` | `boolean` | Enable CSS minification (default: `true`) | | `minifyHTML` | `boolean` | Enable HTML minification (default: `true`) | | `preloadModules` | `boolean` | Experimental: Add deep, optimized [``](https://developers.google.com/web/updates/2017/12/modulepreload) tags into your HTML. (default: `false`) | | `preloadCSS` | `boolean` | Experimental: Eliminate `.css.proxy.js` files and combine imported CSS into one file for better network performance (default: `false`) | | `preloadCSSFileName` | `string` | If preloading CSS, change the name of the generated CSS file. Used only in conjunction with `preloadCSS: true` (default: `/imported-styles.css`) | | `target` | `string,string[]` | The language target(s) to transpile to. This can be a single string (ex: "es2018") or an array of strings (ex: ["chrome58","firefox57"]). If undefined, no transpilation will be done. See [esbuild documentation](https://github.com/evanw/esbuild) for more. | ================================================ FILE: plugins/plugin-optimize/lib/css.js ================================================ const fs = require('fs'); const path = require('path'); const {parse} = require('es-module-lexer'); const csso = require('csso'); /** Early-exit function that determines, given a set of JS files, if CSS is being imported */ function hasCSSImport(files) { for (const file of files) { const code = fs.readFileSync(file, 'utf-8'); const [imports] = parse(code); for (const {s, e} of imports.filter(({d}) => d === -1)) { const spec = code.substring(s, e); if (spec.endsWith('.css.proxy.js')) return true; // exit as soon as we find one } } return false; } exports.hasCSSImport = hasCSSImport; /** * Scans JS for CSS imports, and embeds only what’s needed * * import 'global.css' -> (removed; loaded in HTML) * import url from 'global.css' -> const url = 'global.css' * import {foo, bar} from 'local.module.css' -> const {foo, bar} = 'local.module.css' */ function transformCSSProxy(file, originalCode) { const filePath = path.dirname(file); let code = originalCode; const getProxyImports = (code) => parse(code)[0] .filter(({d}) => d === -1) // discard dynamic imports (> -1) and import.meta (-2) .filter(({s, e}) => code.substring(s, e).endsWith('.css.proxy.js')); // only accept .css.proxy.js files // iterate through proxy imports let proxyImports = getProxyImports(code); while (proxyImports.length) { const {s, e, ss, se} = proxyImports[0]; // only transform one at a time, because every transformation requires re-parsing (unless you created an ellaborate mechanism to keep track of character counts but IMO parsing is simpler/cheaper) const originalImport = code.substring(s, e); const importedFile = originalImport.replace(/\.proxy\.js$/, ''); const importNamed = code .substring(ss, se) .replace(code.substring(s - 1, e + 1), '') // remove import .replace(/^import\s+/, '') // remove keyword .replace(/\s*from.*$/, '') // remove other keyword .replace(/\*\s+as\s+/, '') // sanitize star imports .trim(); // transform JS if (!importNamed) { // option 1: no transforms needed code = code.replace(new RegExp(`${code.substring(ss, se)};?\n?`), ''); } else { if (importedFile.endsWith('.module.css')) { // option 2: transform css modules const proxyCode = fs.readFileSync(path.resolve(filePath, originalImport), 'utf-8'); const matches = proxyCode.match(/^let json\s*=\s*(\{[^\}]+\})/m); if (matches) { code = code.replace( new RegExp(`${code.substring(ss, se).replace(/\*/g, '\\*')};?`), `const ${importNamed.replace(/\*\s+as\s+/, '')} = ${matches[1]};`, ); } } else { // option 3: transfrom normal css code = code.replace( new RegExp(`${code.substring(ss, se)};?`), `const ${importNamed} = '${importedFile}';`, ); } } proxyImports = getProxyImports(code); // re-parse code, continuing until all are transformed } return code; } exports.transformCSSProxy = transformCSSProxy; /** Build CSS File */ function buildImportCSS(manifest, minifyCSS) { // gather list of imported CSS files const allCSSFiles = new Set(); for (const f in manifest) { manifest[f].js.forEach((js) => { if (!js.endsWith('.css.proxy.js')) return; const isCSSModule = js.endsWith('.module.css.proxy.js'); allCSSFiles.add(isCSSModule ? js : js.replace(/\.proxy\.js$/, '')); }); } // read + concat let code = ''; allCSSFiles.forEach((file) => { const contents = fs.readFileSync(file, 'utf-8'); if (file.endsWith('.module.css.proxy.js')) { // css modules const matches = contents.match(/^export let code = *(.*)$/m); if (matches && matches[1]) code += '\n' + matches[1] .trim() .replace(/^('|")/, '') .replace(/('|");?$/, ''); } else { // normal css code += '\n' + contents; fs.unlinkSync(file); // after we‘ve scanned a CSS file, remove it (so it‘s not double-loaded) } }); // sanitize JSON values const css = code.replace(/\\n/g, '\n').replace(/\\"/g, '"'); // minify return minifyCSS ? csso.minify(css).css : css; } exports.buildImportCSS = buildImportCSS; ================================================ FILE: plugins/plugin-optimize/lib/html.js ================================================ /** * Logic for optimizing .html files (note: this will ) */ const fs = require('fs'); const path = require('path'); const hypertag = require('hypertag'); const {injectHTML} = require('node-inject-html'); const {projectURL, isRemoteModule} = require('../util'); const {scanJS} = require('./js'); /** Scan HTML for static imports */ async function scanHTML(htmlFiles, buildDirectory) { const importList = {}; await Promise.all( htmlFiles.map(async (htmlFile) => { // TODO: add debug in plugins? // log(`scanning ${projectURL(file, buildDirectory)} for imports`, 'debug'); const allCSSImports = new Set(); // all CSS imports for this HTML file const allJSImports = new Set(); // all JS imports for this HTML file const entry = new Set(); // keep track of HTML entry files const code = await fs.promises.readFile(htmlFile, 'utf-8'); // hypertag(code, 'link').forEach((link) => { if (!link.href) return; if (isRemoteModule(link.href)) { allCSSImports.add(link.href); } else { const resolvedCSS = link.href[0] === '/' ? path.join(buildDirectory, link.href) : path.join(path.dirname(htmlFile), link.href); allCSSImports.add(resolvedCSS); } }); // `).join('') + '\n', }); } exports.preloadJS = preloadJS; ================================================ FILE: plugins/plugin-optimize/lib/js.js ================================================ /** * Functions for dealing with parsing/transforming JS */ const fs = require('fs'); const path = require('path'); const {parse} = require('es-module-lexer'); const colors = require('kleur/colors'); const {log, projectURL, isRemoteModule} = require('../util'); /** Recursively scan JS for static imports */ function scanJS({file, rootDir, scannedFiles, importList}) { try { // 1. scan file for static imports scannedFiles.add(file); // keep track of scanned files so we never redo work importList.add(file); // make sure import is marked if (isRemoteModule(file)) { // don’t scan remote modules return importList; } let code = fs.readFileSync(file, 'utf-8'); const [imports] = parse(code); imports .filter(({d}) => d === -1) // this is where we discard dynamic imports (> -1) and import.meta (-2) .forEach(({s, e}) => { const specifier = code.substring(s, e); if (isRemoteModule(specifier)) { importList.add(specifier); scannedFiles.add(specifier); // don’t scan remote modules } else { importList.add( specifier.startsWith('/') ? path.join(rootDir, file) : path.resolve(path.dirname(file), specifier), ); } }); // 2. recursively scan imports not yet scanned [...importList] .filter((fileLoc) => !scannedFiles.has(fileLoc)) // prevent infinite loop .forEach((fileLoc) => { scanJS({file: fileLoc, rootDir, scannedFiles, importList}).forEach((newImport) => { importList.add(newImport); }); }); return importList; } catch (err) { log(colors.yellow(` could not locate "${projectURL(file, rootDir)}"`), 'warn'); return importList; } } exports.scanJS = scanJS; ================================================ FILE: plugins/plugin-optimize/package.json ================================================ { "name": "@snowpack/plugin-optimize", "version": "0.2.13", "main": "plugin.js", "license": "MIT", "homepage": "https://github.com/withastro/snowpack/tree/main/plugins/plugin-optimize#readme", "repository": { "type": "git", "url": "https://github.com/withastro/snowpack.git", "directory": "plugins/plugin-optimize" }, "publishConfig": { "access": "public" }, "dependencies": { "csso": "^4.1.0", "es-module-lexer": "^0.3.25", "esbuild": "^0.9.3", "glob": "^7.1.6", "html-minifier": "^4.0.0", "hypertag": "^0.0.3", "mkdirp": "^1.0.4", "kleur": "^4.1.3", "node-inject-html": "^0.0.5", "object.fromentries": "^2.0.2", "p-queue": "^6.6.1" } } ================================================ FILE: plugins/plugin-optimize/plugin.js ================================================ const fs = require('fs'); const path = require('path'); const glob = require('glob'); const {minify: minifyHtml} = require('html-minifier'); const csso = require('csso'); const esbuild = require('esbuild'); const {init} = require('es-module-lexer'); const mkdirp = require('mkdirp'); const PQueue = require('p-queue').default; const {injectHTML} = require('node-inject-html'); const {buildImportCSS, transformCSSProxy} = require('./lib/css'); const {scanHTML, preloadJS} = require('./lib/html'); const {formatManifest, log} = require('./util'); /** * Default optimizer for Snawpack, unless another one is given */ exports.default = function plugin(config, userDefinedOptions) { const options = { minifyJS: true, minifyHTML: true, minifyCSS: true, preloadCSS: false, preloadCSSFileName: '/imported-styles.css', preloadModules: false, ...(userDefinedOptions || {}), }; const CONCURRENT_WORKERS = require('os').cpus().length; async function optimizeFile({file, preloadCSS, target, rootDir}) { const baseExt = path.extname(file).toLowerCase(); // TODO: add debug in plugins? // log(`optimizing ${projectURL(file, rootDir)}…`, 'debug'); // optimize based on extension. if it’s not here, leave as-is switch (baseExt) { case '.css': { const shouldOptimize = options.minifyCSS; if (!shouldOptimize) return; // minify let code = fs.readFileSync(file, 'utf-8'); code = csso.minify(code).css; fs.writeFileSync(file, code, 'utf-8'); break; } case '.js': case '.mjs': { const shouldOptimize = options.preloadCSS || options.minifyJS; if (!shouldOptimize) return; let code = fs.readFileSync(file, 'utf-8'); // embed CSS if (preloadCSS) { code = transformCSSProxy(file, code); } // minify if enabled if (options.minifyJS) { const minified = await esbuild.transform(code, { minify: true, charset: 'utf8', target, }); code = minified.code; fs.writeFileSync(file, code); } fs.writeFileSync(file, code); break; } case '.html': { const shouldOptimize = options.preloadCSS || options.preloadModules || options.minifyHTML; if (!shouldOptimize) return; let code = fs.readFileSync(file, 'utf-8'); // preload CSS if (preloadCSS) { code = injectHTML(code, { headEnd: `\n`, }); } // preload JS if (options.preloadModules) { code = preloadJS({code, file, preloadCSS, rootDir}); } // minify if (options.minifyHTML) { code = minifyHtml(code, { collapseWhitespace: true, keepClosingSlash: true, removeComments: true, }); } fs.writeFileSync(file, code, 'utf-8'); break; } } } return { name: '@snowpack/plugin-optimize', async optimize({buildDirectory}) { // 0. setup await init; let generatedFiles = {}; // 1. index files const allFiles = glob .sync('**/*', { cwd: buildDirectory, ignore: [`${config.buildOptions.metaUrlPath}/*`], nodir: true, }) .map((file) => path.join(buildDirectory, file)); // resolve to root dir // 2. scan imports const manifest = await scanHTML( allFiles.filter((f) => path.extname(f) === '.html'), buildDirectory, ); let preloadCSS = false; // only bother preloading CSS if option is enabled AND there are .css.proxy.js files if (options.preloadCSS) { for (f in manifest) { if ( manifest[f].js && manifest[f].js.findIndex((js) => js.endsWith('.css.proxy.js')) !== -1 ) { preloadCSS = true; break; } } } // 3. optimize all files in parallel const parallelWorkQueue = new PQueue({concurrency: CONCURRENT_WORKERS}); allFiles .filter( (file) => (preloadCSS ? !file.endsWith('.css.proxy.js') : true), // if preloading CSS, don’t optimize .css.proxy.js files ) .forEach((file) => { parallelWorkQueue.add(() => optimizeFile({ file, preloadCSS, rootDir: buildDirectory, target: options.target, }).catch((err) => { log(`Error: ${file} ${err.toString()}`, 'error'); }), ); }); await parallelWorkQueue.onIdle(); // 5. build CSS file if (preloadCSS) { const combinedCSS = buildImportCSS(manifest, options.minifyCSS); const outputCSS = path.join(buildDirectory, options.preloadCSSFileName); await mkdirp(path.dirname(outputCSS)); fs.writeFileSync(outputCSS, combinedCSS, 'utf-8'); generatedFiles.preloadedCSS = outputCSS; } // 6. write manifest fs.writeFileSync( path.join(buildDirectory, config.buildOptions.metaUrlPath, 'optimize-manifest.json'), JSON.stringify( formatManifest({manifest, buildDirectory, generatedFiles, preloadCSS}), undefined, 2, // prettify ), 'utf-8', ); }, }; }; ================================================ FILE: plugins/plugin-optimize/test/plugin.test.js ================================================ const path = require('path'); const fs = require('fs'); const plugin = require('../plugin').default; const {getSnowpackPluginOutputSnapshotSerializer} = require('./serializer'); describe.skip('@snowpack/plugin-optimize', () => { beforeEach(() => { expect.addSnapshotSerializer(getSnowpackPluginOutputSnapshotSerializer(__dirname)); const originalWriteFileSync = fs.writeFileSync; fs.writeFileSync = jest .fn() .mockName('fs.writeFileSync') .mockImplementation((path, ...args) => { if (path.startsWith(__dirname)) return; // write files outside of the current folder originalWriteFileSync(path, ...args); }); console.log = jest.fn().mockName('console.log'); }); it('minimal - no options', async () => { const pluginInstance = plugin({ buildOptions: {metaUrlPath: '_snowpack'}, }); await pluginInstance.optimize({ buildDirectory: path.resolve(__dirname, 'stubs/minimal/'), }); expect(fs.writeFileSync).toMatchSnapshot('fs.writeFileSync'); expect(console.log).toMatchSnapshot('console.log'); }); it('minimal - no minification', async () => { const pluginInstance = plugin( { buildOptions: {metaUrlPath: '_snowpack'}, }, { minifyJS: false, minifyCSS: false, minifyHTML: false, }, ); await pluginInstance.optimize({ buildDirectory: path.resolve(__dirname, 'stubs/minimal/'), }); expect(fs.writeFileSync).toMatchSnapshot('fs.writeFileSync'); expect(console.log).toMatchSnapshot('console.log'); }); it('no HTML minification, with preloadModules', async () => { const pluginInstance = plugin( { buildOptions: {metaUrlPath: '_snowpack'}, }, { minifyHTML: false, preloadModules: true, }, ); await pluginInstance.optimize({ buildDirectory: path.resolve(__dirname, 'stubs/minimal/'), }); expect(fs.writeFileSync).toMatchSnapshot('fs.writeFileSync'); expect(console.log).toMatchSnapshot('console.log'); }); it('minimal - target', async () => { const pluginInstance = plugin( { buildOptions: {metaUrlPath: '_snowpack'}, }, { target: ['es2018'], }, ); await pluginInstance.optimize({ buildDirectory: path.resolve(__dirname, 'stubs/minimal'), }); expect(fs.writeFileSync).toMatchSnapshot('fs.writeFileSync'); expect(console.log).toMatchSnapshot('console.log'); }); }); ================================================ FILE: plugins/plugin-optimize/test/serializer.js ================================================ module.exports = { getSnowpackPluginOutputSnapshotSerializer, }; const {format} = require('util'); const strpAnsi = require('strip-ansi'); /** * Serializer of written files as well as console.log output. * * Both file contents and log output is normalized to account for differences * in UNIX and Windows systems. * * @param string basePath all files written outside this path will be ignored (usually set to __dirname) */ function getSnowpackPluginOutputSnapshotSerializer(basePath) { return { serialize(value) { if (value.getMockName() === 'console.log') { return value.mock.calls .map(toSingleArgument) .map(toNoralizedByteSize) .map(removeColors) .join('\n'); } const calls = value.mock.calls .filter(isLocal) .map(toPathAndStringContent.bind(null, basePath)); return calls .sort((a, b) => { return a[0] < b[0] ? -1 : 1; }) .map(([path, content]) => { return `FILE: ${path}\n${content}`; }) .join( '\n\n--------------------------------------------------------------------------------\n\n', ); }, test(value) { return value && value.mock; }, }; } function toPathAndStringContent(basePath, [path, content]) { const shortPath = path.replace(basePath, '').substr(1); // unix-ify folder separators for Windows const normalizedPath = shortPath.replace(/\\/g, '/'); // unix-ify new lines const normalizedContent = content.toString().replace(/(\\r\\n)/g, '\\n'); return [normalizedPath, normalizedContent]; } function toSingleArgument([output, ...args]) { return format(output, ...args); } function toNoralizedByteSize(output) { return output.replace(/(\s{2,})\d+ bytes/g, '$1XXX bytes'); } function removeColors(output) { return strpAnsi(output); } function isLocal(call) { return call[0].startsWith(__dirname); } ================================================ FILE: plugins/plugin-optimize/test/stubs/minimal/do-not-preload-1.js ================================================ console.error('Error: this file in a comment shouldn’t be preloaded'); ================================================ FILE: plugins/plugin-optimize/test/stubs/minimal/do-not-preload-2.js ================================================ console.error('Error: this entry file shouldn’t be preloaded, either (it’s already in the entry)'); ================================================ FILE: plugins/plugin-optimize/test/stubs/minimal/do-not-preload-3.js ================================================ console.error('Error: this lazy-loaded file should not be preloaded (then it’s not lazy-loaded)'); ================================================ FILE: plugins/plugin-optimize/test/stubs/minimal/esm_example.js ================================================ export default function esm_example() { console.log('example'); return 1; } ================================================ FILE: plugins/plugin-optimize/test/stubs/minimal/index.html ================================================ Minimal test ================================================ FILE: plugins/plugin-optimize/test/stubs/minimal/index.js ================================================ import esm_example from './esm_example.js'; import supportTarget from './target-es2018.js'; import('./do-not-preload-3.js'); (() => { function n(o) { console.log(o); } n('test', supportTarget()); })(); ================================================ FILE: plugins/plugin-optimize/test/stubs/minimal/package.json ================================================ { "name": "plugin-webpack-test-minimal", "main": "src/index.js" } ================================================ FILE: plugins/plugin-optimize/test/stubs/minimal/style.css ================================================ body { border: 1px solid #ffffff; margin: 1px 1px 1px 1px; } ================================================ FILE: plugins/plugin-optimize/test/stubs/minimal/target-es2018.js ================================================ export default function testTarget() { // es2020 - Optional Chaining & Nullish coalescing Operator const foo = { bar: 'bar', }; const bar = null; console.log(foo?.bar, bar ?? 100); return true; } ================================================ FILE: plugins/plugin-optimize/util.js ================================================ /** * Copy/paste from Snowpack utils, at least until there’s some common import */ const path = require('path'); const colors = require('kleur/colors'); const fromEntries = require('object.fromentries'); // Node 10 shim if (!Object.fromEntries) fromEntries.shim(); /** log somethin */ function log(msg, level = 'log') { console[level](`${colors.dim('[@snowpack/plugin-optimize]')} ${msg}`); } exports.log = log; /** determine if remote package or not */ function isRemoteModule(specifier) { return ( specifier.startsWith('//') || specifier.startsWith('http://') || specifier.startsWith('https://') ); } exports.isRemoteModule = isRemoteModule; /** URL relative */ function projectURL(url, buildDirectory) { return path.relative(buildDirectory, url).replace(/\\/g, '/').replace(/^\/?/, '/'); } exports.projectURL = projectURL; /** Remove \ and / from beginning of string */ function removeLeadingSlash(path) { return path.replace(/^[/\\]+/, ''); } exports.removeLeadingSlash = removeLeadingSlash; /** Build Import */ function formatManifest({manifest, buildDirectory, generatedFiles, preloadCSS}) { const format = (url) => (isRemoteModule(url) ? url : projectURL(url, buildDirectory)); const sorted = Object.entries(manifest).map(([k, v]) => { const entry = v.entry.map(format); const css = v.css.map(format); const js = v.js .filter((f) => (preloadCSS && !f.endsWith('.css.proxy.js')) || true) // if preloading CSS, omit .css.proxy.js files .map(format); entry.sort((a, b) => a.localeCompare(b)); css.sort((a, b) => a.localeCompare(b)); js.sort((a, b) => a.localeCompare(b)); return [format(k), {entry, css, js}]; }); sorted.sort((a, b) => a[0].localeCompare(b[0])); return { imports: Object.fromEntries(sorted), generated: Object.fromEntries(Object.entries(generatedFiles).map(([k, v]) => [k, format(v)])), }; } exports.formatManifest = formatManifest; ================================================ FILE: plugins/plugin-postcss/CHANGELOG.md ================================================ # @snowpack/plugin-postcss ## 1.4.3 ### Patch Changes - 8cdad981: [ci] yarn format - f9fc09a8: Provide the 'from' filename when processing postcss (#3493) ## 1.4.2 ### Patch Changes - a6b3b71f: Allow srcPath fallback for older versions of Snowpack (#3484) - 33cd5648: Pass real filepath to transform plugins (#3483) - b87534b2: Remove postcss-cli from plugin-postcss docs (#3412) ## 1.4.1 ### Patch Changes - bc56243c: [PostCSS] Support `glob` property in `dir-dependency` messages (#3404) ## 1.4.0 ### Minor Changes - 4f9da737: Feat: Allow dynamic config in @snowpack/plugin-postcss (#3344) ### Patch Changes - 1823b35f: Chore: update docs to refer to snowpack.config.mjs (#3341) ## 1.3.0 ### Minor Changes - b5bd57f8: Add support for PostCSS dependency messages (#3309) ## 1.2.2 ### Patch Changes - 56c5a231: fix typo in changelog - 40f02cf4: reorder changelog entries ## 1.2.1 ### Patch Changes - 8ac85f6d: postcss: add filepath to id ## 1.2.0 [2021-03-16] - 21461ce5: Update @snowpack/plugin-postcss API (#2854) - 68a66d4e: plugin-postcss: Use API and threadpool instead of CLI. (#2165) _For older releases, check our curated [release update thread](https://github.com/withastro/snowpack/discussions/1183) or the raw [commit history](https://github.com/withastro/snowpack/commits/main/plugins/plugin-postcss)._ ================================================ FILE: plugins/plugin-postcss/README.md ================================================ # @snowpack/plugin-postcss Runs [PostCSS](https://github.com/postcss/postcss) on all `.css` files, including ones generated from Sass, Vue, and Svelte. ### Usage Install @snowpack/plugin-postcss, PostCSS, and your PostCSS plugins (not shown): ``` npm install --save-dev @snowpack/plugin-postcss postcss ``` Then add this plugin to your Snowpack config: ```diff // snowpack.config.mjs export default { + plugins: ['@snowpack/plugin-postcss'], }; ``` Lastly, add a `postcss.config.js` file. By default, @snowpack/plugin-postcss looks for this in the root directory of your project, but you can customize this with the `config` option. ```js module.exports = { plugins: [ // Replace below with your plugins require('cssnano'), require('postcss-preset-env') ], }; ``` ### Plugin Options | Name | Type | Description | | :------- | :----------------: | :-------------------------------------------------------------------------------- | | `input` | `string[]` | File extensions to transform (default: `['.css']`) | | `config` | `string \| object` | (optional) Pass in a PostCSS config object or path to your PostCSS config on disk | ================================================ FILE: plugins/plugin-postcss/package.json ================================================ { "name": "@snowpack/plugin-postcss", "version": "1.4.3", "main": "plugin.js", "license": "MIT", "homepage": "https://github.com/withastro/snowpack/tree/main/plugins/plugin-postcss#readme", "repository": { "type": "git", "url": "https://github.com/withastro/snowpack.git", "directory": "plugins/plugin-postcss" }, "publishConfig": { "access": "public" }, "dependencies": { "minimatch": "^3.0.4", "normalize-path": "^3.0.0", "postcss-load-config": "^3.0.1", "workerpool": "^6.1.2" }, "devDependencies": { "postcss": "^8.3.5" }, "peerDependencies": { "postcss": "*" } } ================================================ FILE: plugins/plugin-postcss/plugin.js ================================================ 'use strict'; const path = require('path'); const workerpool = require('workerpool'); const minimatch = require('minimatch'); const normalizePath = require('normalize-path'); module.exports = function postcssPlugin(snowpackConfig, options) { // options validation if (options) { if (typeof options !== 'object' || Array.isArray(options)) throw new Error('options isn’t an object. Please see README.'); if ( (options.config && typeof options !== 'string' && typeof options !== 'object') || Array.isArray(options) ) throw new Error('options.config must be a config object or a path to a PostCSS config file.'); } let worker, pool; const dependencies = new Map(); return { name: '@snowpack/postcss-transform', async transform({id, srcPath, fileExt, contents}) { let {input = ['.css'], config} = options; if (!input.includes(fileExt) || !contents) return; if (config && typeof config === 'string') { config = path.resolve(config); } pool = pool || workerpool.pool(require.resolve('./worker.js')); worker = worker || (await pool.proxy()); const encodedResult = await worker.transformAsync(contents, { config, filepath: srcPath || id, // note: srcPath will be undefined in snowpack@3.6.1 and older cwd: snowpackConfig.root || process.cwd(), map: snowpackConfig.buildOptions && snowpackConfig.buildOptions.sourceMaps ? { prev: false, annotation: false, inline: false, } : false, }); const {css, map, messages} = JSON.parse(encodedResult); const patterns = new Set(); for (const message of messages) { if (message.type === 'dependency') { patterns.add(normalizePath(message.file)); } else if (message.type === 'dir-dependency') { patterns.add(normalizePath(`${message.dir}/${message.glob || '**/*'}`)); } } dependencies.set(id, patterns); return { code: css, // old API (keep) contents: css, // new API map, }; }, onChange({filePath}) { const normalizedFilePath = normalizePath(filePath); eachId: for (const [id, patterns] of dependencies) { for (const pattern of patterns) { if (minimatch(normalizedFilePath, pattern)) { this.markChanged(id); continue eachId; } } } }, cleanup() { pool && pool.terminate(); }, }; }; ================================================ FILE: plugins/plugin-postcss/test/fixtures/from/from.css ================================================ @from(.foo) { } ================================================ FILE: plugins/plugin-postcss/test/fixtures/from/postcss.config.js ================================================ function pluginFrom() { return { postcssPlugin: 'plugin-from', AtRule(node, {result, Declaration}) { node.replaceWith(new Declaration({prop: 'content', value: `"${result.opts.from}"`})); }, }; } pluginFrom.postcss = true; module.exports = { plugins: [pluginFrom], }; ================================================ FILE: plugins/plugin-postcss/test/fixtures/from/style.css ================================================ body { background: #fff; } #root { width: 480px; margin: auto; padding: 16px; box-sizing: border-box; } .content { color: blue; } ================================================ FILE: plugins/plugin-postcss/test/fixtures/postcss.config.js ================================================ module.exports = { plugins: [ require('cssnano')({ preset: 'default', }), ], }; ================================================ FILE: plugins/plugin-postcss/test/fixtures/style.css ================================================ body { background: #fff; } #root { box-sizing: border-box; margin: auto; padding: 16px; width: 480px; } .content { color: blue; } ================================================ FILE: plugins/plugin-postcss/test/plugin.test.js ================================================ const fs = require('fs'); const path = require('path'); const plugin = require('../plugin.js'); const cssPath = path.join(__dirname, 'fixtures', 'style.css'); const minCssPath = path.join(__dirname, 'fixtures', 'style.min.css'); const cssContent = fs.readFileSync(cssPath, 'utf8'); const minCssContent = fs.readFileSync(minCssPath, 'utf8'); describe('@snowpack/plugin-postcss', () => { test('loads postcss config with no options', async () => { const pluginInstance = plugin({root: path.join(__dirname, 'fixtures')}, {}); const transformCSSResults = await pluginInstance.transform({ id: cssPath, fileExt: path.extname(cssPath), contents: cssContent, }); expect(transformCSSResults.code).toBe(minCssContent); // TODO: remove this? expect(transformCSSResults.contents).toBe(minCssContent); expect(transformCSSResults.map).toBe(undefined); await pluginInstance.cleanup(); }); test('accepts a path to a config file', async () => { const pluginInstance = plugin( {}, {config: path.join(__dirname, 'fixtures', 'postcss.config.js')}, ); const transformCSSResults = await pluginInstance.transform({ id: cssPath, fileExt: path.extname(cssPath), contents: cssContent, }); expect(transformCSSResults.code).toBe(minCssContent); // TODO: remove this? expect(transformCSSResults.contents).toBe(minCssContent); expect(transformCSSResults.map).toBe(undefined); await pluginInstance.cleanup(); }); test('produces source maps with sourceMaps: true', async () => { const pluginInstance = plugin( {root: path.join(__dirname, 'fixtures'), buildOptions: {sourceMaps: true}}, {}, ); const transformCSSResults = await pluginInstance.transform({ id: cssPath, fileExt: path.extname(cssPath), contents: cssContent, }); expect(transformCSSResults.code).toBe(minCssContent); // TODO: remove this? expect(transformCSSResults.contents).toBe(minCssContent); expect(transformCSSResults.map).toEqual( // a raw source map object expect.objectContaining({ version: expect.any(Number), mappings: expect.any(String), }), ); await pluginInstance.cleanup(); }); test('bails with empty input array', async () => { const pluginInstance = plugin({root: path.join(__dirname, 'fixtures')}, {input: []}); const transformCSSResults = await pluginInstance.transform({ id: cssPath, fileExt: path.extname(cssPath), contents: cssContent, }); expect(transformCSSResults).toBeFalsy(); await pluginInstance.cleanup(); }); test('allows dynamic config', async () => { // important: make sure to NOT set {root:} here so it doesn’t automatically pick up fixtures/postcss.config.js const pluginInstance = plugin({}, {config: {plugins: {cssnano: {}}}}); const transformCSSResults = await pluginInstance.transform({ id: cssPath, fileExt: path.extname(cssPath), contents: cssContent, }); expect(transformCSSResults.code).toBe(minCssContent); // TODO: remove this? expect(transformCSSResults.contents).toBe(minCssContent); expect(transformCSSResults.map).toBe(undefined); await pluginInstance.cleanup(); }); test('the correct "from" is provided', async () => { const pluginInstance = plugin( {root: path.join(__dirname, 'fixtures', 'from')}, { config: path.join(__dirname, 'fixtures', 'from', 'postcss.config.js'), }, ); const cssPath = path.join(__dirname, 'fixtures', 'from', 'style.css'); const cssContent = fs.readFileSync(cssPath, 'utf8'); let transformCSSResults = await pluginInstance.transform({ id: cssPath, fileExt: path.extname(cssPath), contents: cssContent, }); const fromCssPath = path.join(__dirname, 'fixtures', 'from', 'from.css'); const fromCssContent = fs.readFileSync(fromCssPath, 'utf8'); transformCSSResults = await pluginInstance.transform({ id: fromCssPath, fileExt: path.extname(fromCssPath), contents: fromCssContent, }); expect(transformCSSResults.code).toEqual(expect.stringContaining('from.css')); await pluginInstance.cleanup(); }); }); ================================================ FILE: plugins/plugin-postcss/worker.js ================================================ 'use strict'; const workerpool = require('workerpool'); const postcss = require('postcss'); const postcssrc = require('postcss-load-config'); const loadPlugins = require('postcss-load-config/src/plugins.js'); const loadOptions = require('postcss-load-config/src/options.js'); const processMap = new Map(); async function transformAsync(css, {filepath, config, cwd, map}) { let process = null; const key = config + '-' + cwd; // Initialize processor. `config`, `cwd` won't change until Snowpack is restarted if (!processMap.has(key)) { let plugins = []; let options = {}; if (typeof config === 'object') { plugins = loadPlugins(config); options = loadOptions(config); } else { const rc = await postcssrc({}, config || cwd); plugins = rc.plugins; options = rc.options; } const processor = postcss(plugins); process = (css, filepath, map) => processor.process(css, {...options, from: filepath, map}); processMap.set(key, process); } process = processMap.get(key); const result = await process(css, filepath, map); return JSON.stringify({css: result.css, map: result.map, messages: result.messages}); } // create a worker and register public functions workerpool.worker({ transformAsync, }); ================================================ FILE: plugins/plugin-react-refresh/CHANGELOG.md ================================================ # @snowpack/plugin-react-refresh ## 2.5.0 ### Minor Changes - 1b33af55: plugin-react-refresh: add custom babel config option (#2943) ## 2.4.2 ### Patch Changes - 857d73cb: [ci] yarn format - ceb516d0: [plugin-react-refresh] Solve the problem with MobX observer() HOC (#3058) - 56c5a231: fix typo in changelog - a82cb6b1: Revert "[plugin-react-refresh] Solve the problem with MobX observer() HOC (#3015)" - b07d815b: [ci] yarn format - 6e4bb7fe: [plugin-react-refresh] Solve the problem with MobX observer() HOC (#3015) ## 2.4.1 ### Patch Changes - bea1c56c: Simplify. cleanup, enhance snowpack internals (#2707) - ca16821f: Parse multiline tags (#2406) - 353da2cb: [ci] yarn format - f6b30dea: Disable react-refresh during ssr. (#2376) _For older releases, check our curated [release update thread](https://github.com/withastro/snowpack/discussions/1183) or the raw [commit history](https://github.com/withastro/snowpack/commits/main/plugins/plugin-react-refresh)._ ================================================ FILE: plugins/plugin-react-refresh/README.md ================================================ # @snowpack/plugin-react-refresh Transforms JavaScript files containing React components automatically to enable React Fast Refresh via Snowpack's HMR API. ``` npm install --save-dev @snowpack/plugin-react-refresh ``` ## Setup ```js // snowpack.config.mjs export default { plugins: [ '@snowpack/plugin-react-refresh', { /* options: see below */ }, ], }; ``` ## Plugin Options | Name | Type | Description | | :------ | :-------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `babel` | `boolean` or `object` | By default, this plugin uses Babel to add Fast-Refresh code to eligible JS files. If you want to configure & run this yourself, set `"babel": false"`. Alternatively, you can pass a custom Babel configuration object to enhance or override the defaults. Most users won't need this. | ## How it Works This plugin will automatically inject HMR event handlers into any file containing a React component. In most applications, you'll still want some top-level `import.meta.hot` handling code in your application for any non-React file updates. In our Create Snowpack App templates, this would be the HMR handling snippet found in `src/index.js`. ================================================ FILE: plugins/plugin-react-refresh/package.json ================================================ { "name": "@snowpack/plugin-react-refresh", "version": "2.5.0", "main": "plugin.js", "license": "MIT", "homepage": "https://github.com/withastro/snowpack/tree/main/plugins/plugin-react-refresh#readme", "repository": { "type": "git", "url": "https://github.com/withastro/snowpack.git", "directory": "plugins/plugin-react-refresh" }, "publishConfig": { "access": "public" }, "dependencies": { "@babel/core": "^7.14.0", "@babel/plugin-syntax-class-properties": "^7.10.0", "react-refresh": "^0.9.0" }, "peerDependencies": { "react": ">=16.9.0", "react-dom": ">=16.9.0" }, "gitHead": "a01616bb0787d56cd782f94cecf2daa12c7594e4" } ================================================ FILE: plugins/plugin-react-refresh/plugin.js ================================================ /** * @snowpack/plugin-react-refresh (Fast Refresh) * Based on details provided by: * - https://github.com/facebook/react/issues/16604#issuecomment-528663101 * - https://github.com/vitejs/vite-plugin-react (see LICENSE) */ const fs = require('fs'); const reactRefreshLoc = require.resolve('react-refresh/cjs/react-refresh-runtime.development.js'); const reactRefreshCode = fs .readFileSync(reactRefreshLoc, {encoding: 'utf-8'}) .replace(`process.env.NODE_ENV`, JSON.stringify('development')); function transformHtml(contents) { return contents.replace(/()/s, function (match, p1) { return `${p1} `; }); } const babel = require('@babel/core'); const IS_FAST_REFRESH_ENABLED = /\$RefreshReg\$\(/; async function transformJs(contents, id, cwd, babelConfig) { let fastRefreshEnhancedCode; if (babelConfig === false) { fastRefreshEnhancedCode = contents; } else if (IS_FAST_REFRESH_ENABLED.test(contents)) { // Warn in case someone has a bad setup, and to help older users upgrade. console.warn( `[@snowpack/plugin-react-refresh] ${id}\n"react-refresh/babel" plugin no longer needed in your babel config, safe to remove.`, ); fastRefreshEnhancedCode = contents; } else { const {plugins = [], ...restConfig} = babelConfig instanceof Object ? babelConfig : {}; const {code} = await babel.transformAsync(contents, { cwd, filename: id, ast: false, compact: false, sourceMaps: false, configFile: false, babelrc: false, plugins: [ [require('react-refresh/babel'), {skipEnvCheck: true}], require('@babel/plugin-syntax-class-properties'), ...plugins, ], ...restConfig, }); fastRefreshEnhancedCode = code; } // If fast refresh markup wasn't added, just return the original content. if (!fastRefreshEnhancedCode || !IS_FAST_REFRESH_ENABLED.test(fastRefreshEnhancedCode)) { return contents; } return ` /** React Refresh: Setup **/ if (import.meta.hot) { if (!window.$RefreshReg$ || !window.$RefreshSig$ || !window.$RefreshRuntime$) { console.warn('@snowpack/plugin-react-refresh: HTML setup script not run. React Fast Refresh only works when Snowpack serves your HTML routes. You may want to remove this plugin.'); } else { var prevRefreshReg = window.$RefreshReg$; var prevRefreshSig = window.$RefreshSig$; window.$RefreshReg$ = (type, id) => { window.$RefreshRuntime$.register(type, ${JSON.stringify(id)} + " " + id); } window.$RefreshSig$ = window.$RefreshRuntime$.createSignatureFunctionForTransform; } } ${fastRefreshEnhancedCode} /** React Refresh: Connect **/ if (import.meta.hot) { window.$RefreshReg$ = prevRefreshReg window.$RefreshSig$ = prevRefreshSig import.meta.hot.accept(() => { window.$RefreshRuntime$.performReactRefresh() }); }`; } module.exports = function reactRefreshTransform(snowpackConfig, {babel}) { return { name: '@snowpack/plugin-react-refresh', transform({contents, fileExt, id, isDev, isHmrEnabled, isSSR}) { // Use long-form "=== false" to handle older Snowpack versions if (isHmrEnabled === false) { return; } if (!isDev) { return; } // While server-side rendering, the fast-refresh code is not needed. if (fileExt === '.js' && !isSSR) { return transformJs(contents, id, snowpackConfig.root || process.cwd(), babel); } if (fileExt === '.html') { return transformHtml(contents); } }, }; }; ================================================ FILE: plugins/plugin-react-refresh/test/__snapshots__/plugin.test.js.snap ================================================ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`@snowpack/plugin-react-refresh don't transform when disabled: html 1`] = `undefined`; exports[`@snowpack/plugin-react-refresh don't transform when disabled: js 1`] = `undefined`; exports[`@snowpack/plugin-react-refresh transform js and html when babel is disabled: html 1`] = ` "
" `; exports[`@snowpack/plugin-react-refresh transform js and html when babel is disabled: js 1`] = ` "import React from 'react'; function App() { return /* @__PURE__ */ React.createElement('div', null, 'React App'); } export default App; " `; exports[`@snowpack/plugin-react-refresh transform js and html when hmr is disabled: html 1`] = `undefined`; exports[`@snowpack/plugin-react-refresh transform js and html when hmr is disabled: js 1`] = `undefined`; exports[`@snowpack/plugin-react-refresh transform js and html when running in SSR: html 1`] = ` "
" `; exports[`@snowpack/plugin-react-refresh transform js and html when running in SSR: js 1`] = `undefined`; exports[`@snowpack/plugin-react-refresh transform js and html: html 1`] = ` "
" `; exports[`@snowpack/plugin-react-refresh transform js and html: js 1`] = ` " /** React Refresh: Setup **/ if (import.meta.hot) { if (!window.$RefreshReg$ || !window.$RefreshSig$ || !window.$RefreshRuntime$) { console.warn('@snowpack/plugin-react-refresh: HTML setup script not run. React Fast Refresh only works when Snowpack serves your HTML routes. You may want to remove this plugin.'); } else { var prevRefreshReg = window.$RefreshReg$; var prevRefreshSig = window.$RefreshSig$; window.$RefreshReg$ = (type, id) => { window.$RefreshRuntime$.register(type, \\"stub.js\\" + \\" \\" + id); } window.$RefreshSig$ = window.$RefreshRuntime$.createSignatureFunctionForTransform; } } import React from 'react'; function App() { return /* @__PURE__ */React.createElement('div', null, 'React App'); } _c = App; export default App; var _c; $RefreshReg$(_c, \\"App\\"); /** React Refresh: Connect **/ if (import.meta.hot) { window.$RefreshReg$ = prevRefreshReg window.$RefreshSig$ = prevRefreshSig import.meta.hot.accept(() => { window.$RefreshRuntime$.performReactRefresh() }); }" `; ================================================ FILE: plugins/plugin-react-refresh/test/plugin.test.js ================================================ mockBabel(); const path = require('path'); const fs = require('fs'); const pluginReactRefresh = require('../plugin'); const htmlFilePath = path.resolve(__dirname, './stubs/stub.html'); const htmlFileContent = fs.readFileSync(htmlFilePath, { encoding: 'utf-8', }); const htmlTransformOptions = { contents: htmlFileContent, fileExt: '.html', id: 'stub.html', isDev: true, isHmrEnabled: true, isSSR: false, }; const jsFilePath = path.resolve(__dirname, './stubs/stub.js'); const jsFileContent = fs.readFileSync(jsFilePath, { encoding: 'utf-8', }); const jsTransformOptions = { contents: jsFileContent, fileExt: '.js', id: 'stub.js', isDev: true, isHmrEnabled: true, isSSR: false, }; function mockBabel() { jest.mock('@babel/core'); const babel = require('@babel/core'); babel.transformAsync = jest .fn() .mockName('babel.transformAsync') .mockImplementation(async (contents, options, ...args) => { options.plugins = (options.plugins || []).map((plugin) => { if (Array.isArray(plugin)) { if (plugin[1]) { plugin[1].skipEnvCheck = true; } return plugin; } // Fix: React Refresh Babel transform should only be enabled in development environment. // Instead, the environment is: "test". // If you want to override this check, pass {skipEnvCheck: true} as plugin options. return [plugin, {skipEnvCheck: true}]; }); // Stop `jest.requireActual('@babel/core').transformAsync()` from requiring mocked babel function jest.unmock('@babel/core'); const ret = await jest .requireActual('@babel/core') .transformAsync(contents, options, ...args); mockBabel(); return ret; }); } async function testPluginInstance(pluginInstance, overrides = {}) { const pluginTransform = pluginInstance.transform; expect(await pluginTransform({...htmlTransformOptions, ...overrides})).toMatchSnapshot('html'); expect(await pluginTransform({...jsTransformOptions, ...overrides})).toMatchSnapshot('js'); } afterEach(() => { jest.restoreAllMocks(); }); describe('@snowpack/plugin-react-refresh', () => { test('transform js and html', async () => { const pluginInstance = pluginReactRefresh({}, {babel: true}); await testPluginInstance(pluginInstance); }); test("don't transform when disabled", async () => { const pluginInstance = pluginReactRefresh({}, {babel: true}); await testPluginInstance(pluginInstance, {isDev: false}); }); test('transform js and html when hmr is disabled', async () => { const pluginInstance = pluginReactRefresh({}, {babel: true}); await testPluginInstance(pluginInstance, {isHmrEnabled: false}); }); test('transform js and html when running in SSR', async () => { const pluginInstance = pluginReactRefresh({}, {babel: true}); await testPluginInstance(pluginInstance, {isSSR: true}); }); test('transform js and html when babel is disabled', async () => { const pluginInstance = pluginReactRefresh( { devOptions: { hmr: true, }, }, {babel: false}, ); await testPluginInstance(pluginInstance); }); test('include custom babel config', async () => { const babel = require('@babel/core'); const mockTransformAsync = jest.fn(() => Promise.resolve({code: ''})); jest.spyOn(babel, 'transformAsync').mockImplementation(mockTransformAsync); const babelConfig = { compact: true, plugins: ['@babel/plugin-syntax-top-level-await'], }; const pluginInstance = pluginReactRefresh({}, {babel: babelConfig}); await pluginInstance.transform({...jsTransformOptions}); expect(mockTransformAsync).toHaveBeenCalledTimes(1); expect(mockTransformAsync.mock.calls[0][1]).toHaveProperty('compact', true); expect(mockTransformAsync.mock.calls[0][1].plugins).toContain( '@babel/plugin-syntax-top-level-await', ); }); }); ================================================ FILE: plugins/plugin-react-refresh/test/stubs/stub.html ================================================
================================================ FILE: plugins/plugin-react-refresh/test/stubs/stub.js ================================================ import React from 'react'; function App() { return /* @__PURE__ */ React.createElement('div', null, 'React App'); } export default App; ================================================ FILE: plugins/plugin-run-script/README.md ================================================ # @snowpack/plugin-run-script Run any CLI command as a part of Snowpack’s dev server and production build. Useful for languages not supported by [Snowpack plugins](https://www.snowpack.dev/plugins). This replaces the old `run:*` scripts in your Snowpack config. Usage: ```bash npm i @snowpack/plugin-run-script ``` Then add the plugin to your Snowpack config: ```js // snowpack.config.mjs export default { plugins: [ [ '@snowpack/plugin-run-script', { cmd: 'sass src/css:public/css --no-source-map', // production build command watch: 'sass --watch src/css:public/css --no-source-map', // (optional) dev server command }, ], ], }; ``` Supply any CLI command in `cmd`. Note that this is the same as running the command yourself in your project root folder (i.e. you can reference any global packages as well as npm script). ## Plugin Options | Name | Type | Description | | :------- | :------------------------ | :-------------------------------------------------------------------------- | | `cmd` | `string` | The CLI command to run. Note that this will run **before** Snowpack builds. | | `name` | `string` | (optional) Set name of console output, default is program name. | | `watch` | `string` | (optional) A watch command to run during the dev server. | | `output` | `"stream" or "dashboard"` | (optional) Set how the output should be recorded during dev. | ================================================ FILE: plugins/plugin-run-script/package.json ================================================ { "version": "2.3.0", "name": "@snowpack/plugin-run-script", "main": "plugin.js", "license": "MIT", "homepage": "https://github.com/withastro/snowpack/tree/main/plugins/plugin-run-script#readme", "repository": { "type": "git", "url": "https://github.com/withastro/snowpack.git", "directory": "plugins/plugin-run-script" }, "publishConfig": { "access": "public" }, "dependencies": { "execa": "^5.1.1", "npm-run-path": "^4.0.1" } } ================================================ FILE: plugins/plugin-run-script/plugin.js ================================================ const execa = require('execa'); const npmRunPath = require('npm-run-path'); const CLEAR_SEQUENCES = ['\x1Bc', '\x1B[2J\x1B[0;0f']; function runScriptPlugin(snowpackConfig, {name, cmd, watch, output}) { const [cmdProgram] = cmd.split(' '); const watchCmd = watch && watch.replace('$1', cmd); return { name: name || cmdProgram, async run({isDev, log}) { const workerPromise = execa.command(isDev ? watchCmd || cmd : cmd, { env: npmRunPath.env(), extendEnv: true, shell: true, windowsHide: false, cwd: snowpackConfig.root || process.cwd(), }); const {stdout, stderr} = workerPromise; function dataListener(chunk) { let stdOutput = chunk.toString(); if (output === 'stream') { log('CONSOLE_INFO', {msg: stdOutput}); return; } if (CLEAR_SEQUENCES.some((s) => stdOutput.includes(s))) { log('WORKER_RESET', {}); for (let s of CLEAR_SEQUENCES) { stdOutput = stdOutput.replace(s, ''); } } if (cmdProgram === 'tsc') { const errorMatch = stdOutput.match(/Found (\d+) error/); if (errorMatch) { if (errorMatch[1] === '0') { stdOutput = stdOutput.trim(); } } } log('WORKER_MSG', {level: 'log', msg: stdOutput}); } stdout && stdout.on('data', dataListener); stderr && stderr.on('data', dataListener); return workerPromise; }, }; } module.exports = runScriptPlugin; ================================================ FILE: plugins/plugin-run-script/test/plugin.test.js ================================================ const plugin = require('../plugin.js'); const {EventEmitter} = require('events'); jest.mock('execa'); const execa = require('execa'); describe('plugin-run-script', () => { const DEFAULT_OPTIONS = { cmd: 'CMD', watch: '$1 --additional-test-watch-options', }; let execaResult, execaFn; beforeEach(() => { execa.command.mockClear(); execaResult = { stderr: new EventEmitter(), stdout: new EventEmitter(), // Execa is weird, and returns a promise that also has other properties. Fake that here. catch: () => { return execaResult; }, }; execaFn = jest.fn().mockName('execa.command').mockReturnValue(execaResult); execa.command = execaFn; }); test('returns the execa command promise', async () => { const p = plugin({}, DEFAULT_OPTIONS); const result = await p.run({isDev: false, log: jest.fn}); expect(result).toEqual(execaResult); }); test('calls the given "cmd" command when isDev=false', async () => { const p = plugin({}, {cmd: 'CMD'}); await p.run({isDev: false, log: jest.fn}); expect(execaFn.mock.calls[0][0]).toMatch('CMD'); }); test('calls the given "watch" command when isDev=true', async () => { const p = plugin({}, {cmd: 'CMD', watch: '$1 --additional-test-watch-options'}); await p.run({isDev: true, log: jest.fn}); expect(execaFn.mock.calls[0][0]).toMatch('CMD --additional-test-watch-options'); }); test('handles command output in "stream" mode', async () => { const logFn = jest.fn(); const p = plugin({}, {...DEFAULT_OPTIONS, output: 'stream'}); await p.run({isDev: false, log: logFn}); execaResult.stdout.emit('data', Buffer.from('STDOUT_TEST_MESSAGE')); execaResult.stderr.emit('data', Buffer.from('STDERR_TEST_MESSAGE')); expect(logFn.mock.calls).toEqual([ ['CONSOLE_INFO', {msg: 'STDOUT_TEST_MESSAGE'}], ['CONSOLE_INFO', {msg: 'STDERR_TEST_MESSAGE'}], ]); }); test('handles command output in "dashboard" mode', async () => { const logFn = jest.fn(); const p = plugin({}, {...DEFAULT_OPTIONS, output: 'dashboard'}); await p.run({isDev: false, log: logFn}); execaResult.stdout.emit('data', Buffer.from('STDOUT_TEST_MESSAGE')); execaResult.stderr.emit('data', Buffer.from('STDERR_TEST_MESSAGE')); expect(logFn.mock.calls).toEqual([ ['WORKER_MSG', {level: 'log', msg: 'STDOUT_TEST_MESSAGE'}], ['WORKER_MSG', {level: 'log', msg: 'STDERR_TEST_MESSAGE'}], ]); }); test('handles clear character output in "dashboard" mode', async () => { const logFn = jest.fn(); const p = plugin({}, {...DEFAULT_OPTIONS, output: 'dashboard'}); await p.run({isDev: false, log: logFn}); execaResult.stderr.emit('data', Buffer.from('\u001bcTEST_CLEAR_MESSAGE')); execaResult.stderr.emit('data', Buffer.from('\x1BcTEST_CLEAR_MESSAGE')); expect(logFn.mock.calls).toEqual([ ['WORKER_RESET', {}], ['WORKER_MSG', {level: 'log', msg: 'TEST_CLEAR_MESSAGE'}], ['WORKER_RESET', {}], ['WORKER_MSG', {level: 'log', msg: 'TEST_CLEAR_MESSAGE'}], ]); }); test('modify plugin name when specify name', async () => { const p = plugin({}, {...DEFAULT_OPTIONS, output: 'dashboard', name: 'test'}); expect(p.name).toBe('test'); }); }); ================================================ FILE: plugins/plugin-sass/CHANGELOG.md ================================================ # @snowpack/plugin-sass ## 1.4.0 ### Minor Changes - c71a3888: Improve @snowpack/plugin-sass resolution (#2964) ## 1.3.1 ### Patch Changes - 85377715: fix wrong argument to parseCompilerOption(array[]) (#2547) - edef1986: Fix Sass partial changes not triggering recompiles in Dev (#2792) - b0c6b5a0: [ci] yarn format - 549c3ca5: Properly find npm install sass when running outside of snowpack project (#2812) - 397f06e6: SASS plugin, fix default argument Array check (#2670) - 123f2c96: add loadPath support via compilerOptions instead of includePaths - cbfe71d2: [ci] yarn format - 6c56f4bd: add includePaths option to plugin-sass (#2443) - 3b4b1a06: add warning if compilerOptions is used - f94f5a55: [ci] yarn format - b702f698: more testing cleanup - 7b38b486: get tests passing - 71bb73b5: [ci] yarn format - 48d8b9c7: skip failing windows sass test _For older releases, check our curated [release update thread](https://github.com/withastro/snowpack/discussions/1183) or the raw [commit history](https://github.com/withastro/snowpack/commits/main/plugins/plugin-sass)._ ================================================ FILE: plugins/plugin-sass/README.md ================================================ # @snowpack/plugin-sass This plugin adds [Sass](https://sass-lang.com/) support to any Snowpack project. With this plugin, you can import any `*.scss` or `*.sass` Sass file from JavaScript and have it compile to CSS. This plugin also adds support for `.module.scss` Sass Modules. [Learn more.](https://www.snowpack.dev/reference/supported-files) #### A Note on Sass Implementations Sass is interesting in that multiple compilers are available: [sass](https://www.npmjs.com/package/sass) (written in Dart) & [node-sass](https://www.npmjs.com/package/node-sass) (written in JavaScript). Both packages run on Node.js and both are popular on npm. However, [node-sass is now considered deprecated](https://github.com/sass/node-sass/issues/2952). **This plugin was designed to work with the `sass` package.** `sass` is automatically installed with this plugin as a direct dependency, so no extra effort is required on your part. ## Usage ```bash npm i @snowpack/plugin-sass ``` Then add the plugin to your Snowpack config: ```js // snowpack.config.mjs export default { plugins: [ [ '@snowpack/plugin-sass', { /* see options below */ }, ], ], }; ``` ## Plugin Options | Name | Type | Description | | :------------------ | :-------: | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `native` | `boolean` | If `true`, the plugin will ignore the npm version of sass installed locally for the native Sass CLI [installed separately](https://sass-lang.com/install). This involves extra setup, but the result can be [up to 9× faster.](https://stackoverflow.com/a/56422541) (default: `false`). | | `compilerOptions.*` | `object` | Pass [Sass options][sass-options] directly to the Sass compiler (see `compilerOptions`). | ### `compilerOptions` These options are camelCased equivalents of the [Sass CLI Options][sass-options]. The options listed here are safe for use. The other flags not listed here may cause issues or conflicts with Snowpack and/or other plugins; use at your discretion. | Name | Type | Description | | :--------------- | :----------------------------: | :------------------------------------------------------------------------------------------------------ | | `loadPath` | `string, string[]` | Add directories to Sass's load path, to support looking up and loading partials (etc.) by name. | | `style` | `'expanded'` \| `'compressed'` | The output style. Specify `'compressed'` to enable Sass’ built-in minification (default: `'expanded'`). | | `sourceMap` | `boolean` | Enable / disable source maps (default: `true`). | | `sourceMapUrls` | `'relative'` \| `'absolute'` | How to link from source maps to source files (default: `'relative'`). | | `embedSources` | `boolean` | Embed source file contents in source maps (default: `false`). | | `embedSourceMap` | `boolean` | Embed source map contents in CSS (default: `false`). | | `charset` | `boolean` | Emit a `@charset` or BOM for CSS with non-ASCII characters. (default: `true`). | | `update` | `boolean` | Compile only out-of-date stylesheets (default: `false`). | [sass-options]: https://sass-lang.com/documentation/cli/dart-sass#options ================================================ FILE: plugins/plugin-sass/package.json ================================================ { "version": "1.4.0", "name": "@snowpack/plugin-sass", "main": "plugin.js", "license": "MIT", "homepage": "https://github.com/withastro/snowpack/tree/main/plugins/plugin-sass#readme", "repository": { "type": "git", "url": "https://github.com/withastro/snowpack.git", "directory": "plugins/plugin-sass" }, "publishConfig": { "access": "public" }, "dependencies": { "execa": "^5.1.1", "find-up": "^5.0.0", "npm-run-path": "^4.0.1", "sass": "^1.3.0" } } ================================================ FILE: plugins/plugin-sass/plugin.js ================================================ const fs = require('fs'); const path = require('path'); const execa = require('execa'); const npmRunPath = require('npm-run-path'); const findUp = require('find-up'); const IMPORT_REGEX = /\@(use|import|forward)\s*['"](.*?)['"]/g; const PARTIAL_REGEX = /([\/\\])_(.+)(?![\/\\])/; function stripFileExtension(filename) { return filename.split('.').slice(0, -1).join('.'); } function findChildPartials(pathName, fileName, fileExt) { const dirPath = path.parse(pathName).dir; // Prepend a "_" to signify a partial. if (!fileName.startsWith('_')) { fileName = '_' + fileName; } // Add on the file extension if it is not already used. if (!fileName.endsWith('.scss') || !fileName.endsWith('.sass')) { fileName += fileExt; } const filePath = path.resolve(dirPath, fileName); let contents = ''; try { contents = fs.readFileSync(filePath, 'utf8'); } catch (err) {} return contents; } function scanSassImports(fileContents, filePath, fileExt, partials = new Set()) { // TODO: Replace with matchAll once Node v10 is out of TLS. // const allMatches = [...result.matchAll(new RegExp(HTML_JS_REGEX))]; const allMatches = []; let match; const regex = new RegExp(IMPORT_REGEX); while ((match = regex.exec(fileContents))) { allMatches.push(match); } // return all imports, resolved to true files on disk. allMatches .map((match) => match[2]) .filter((s) => s.trim()) // Avoid node packages and core sass libraries. .filter((s) => !s.includes('node_modules') && !s.includes('sass:')) .forEach((fileName) => { let pathName = path.resolve(path.dirname(filePath), fileName); if (partials.has(pathName)) { return; } // Add this partial to the main list being passed to avoid duplicates. partials.add(pathName); // If it is a directory then look for an _index file. try { if (fs.lstatSync(pathName).isDirectory()) { fileName = 'index'; pathName += '/' + fileName; } } catch (err) {} // Recursively find any child partials that have not already been added. const partialsContent = findChildPartials(pathName, fileName, fileExt); if (partialsContent) { const childPartials = scanSassImports(partialsContent, pathName, fileExt, partials); partials.add(...childPartials); } }); return partials; } module.exports = function sassPlugin(snowpackConfig, {native, compilerOptions = {}} = {}) { const {root} = snowpackConfig || {}; /** A map of partially resolved imports to the files that imported them. */ const importedByMap = new Map(); function addImportsToMap(filePath, sassImport) { const importedBy = importedByMap.get(sassImport); if (importedBy) { importedBy.add(filePath); } else { importedByMap.set(sassImport, new Set([filePath])); } } return { name: '@snowpack/plugin-sass', resolve: { input: ['.scss', '.sass'], output: ['.css'], }, /** * If any files imported the given file path, mark them as changed. * @private */ _markImportersAsChanged(filePath) { if (importedByMap.has(filePath)) { const importedBy = importedByMap.get(filePath); importedByMap.delete(filePath); for (const importerFilePath of importedBy) { this.markChanged(importerFilePath); } } }, /** * When a file changes, also mark it's importers as changed. * Note that Sass has very lax matching of imports -> files. * Follow these rules to find a match: https://sass-lang.com/documentation/at-rules/use */ onChange({filePath}) { const filePathNoExt = stripFileExtension(filePath); // check exact: "_index.scss" (/a/b/c/foo/_index.scss) this._markImportersAsChanged(filePath); // check no ext: "_index" (/a/b/c/foo/_index) this._markImportersAsChanged(filePathNoExt); // check no underscore: "index.scss" (/a/b/c/foo/index.scss) this._markImportersAsChanged(filePath.replace(PARTIAL_REGEX, '$1$2')); // check no ext, no underscore: "index" (/a/b/c/foo/index) this._markImportersAsChanged(filePathNoExt.replace(PARTIAL_REGEX, '$1$2')); // check folder import: "foo" (/a/b/c/foo) if (filePathNoExt.endsWith('_index')) { const folderPathNoIndex = filePathNoExt.substring(0, filePathNoExt.length - 7); this._markImportersAsChanged(folderPathNoIndex); } }, /** Load the Sass file and compile it to CSS. */ async load({filePath, isDev}) { const fileExt = path.extname(filePath); const contents = fs.readFileSync(filePath, 'utf8'); // Sass partials should never be loaded directly, return nothing to ignore. if (path.basename(filePath).startsWith('_')) { return; } // During development, we need to track changes to Sass dependencies. if (isDev) { const sassImports = scanSassImports(contents, filePath, fileExt); [...sassImports].forEach((imp) => addImportsToMap(filePath, imp)); } const args = ['--stdin']; // If file is `.sass`, use YAML-style. Otherwise, use default. if (fileExt === '.sass') { args.push('--indented'); } // keep track of load paths later const loadPaths = new Set([path.dirname(filePath)]); const DEFAULT_LOAD_PATHS = new Set([root, process.cwd()]); const nodeModulesPath = await findUp('node_modules', { type: 'directory', cwd: root || __dirname, }); if (nodeModulesPath) DEFAULT_LOAD_PATHS.add(nodeModulesPath); // Pass in user-defined options function parseCompilerOption([flag, value]) { let flagName = flag.replace(/[A-Z]/g, (c) => `-${c.toLowerCase()}`); // convert camelCase to kebab-case switch (typeof value) { case 'boolean': { args.push(`--${value === false ? 'no-' : ''}${flagName}`); break; } case 'string': case 'number': { if (flagName === 'loadPath') { loadPaths.add(value); // don’t add these until default load paths are collected } else { args.push(`--${flagName}=${value}`); } break; } default: { if (Array.isArray(value)) { for (const val of value) { parseCompilerOption([flag, val]); } break; } throw new Error( `compilerOptions[${flag}] value not supported. Must be string, number, or boolean.`, ); } } } Object.entries(compilerOptions).forEach(parseCompilerOption); // --load-path for (const dir of loadPaths) { args.push(`--load-path=${dir}`); // load user-specified loadPaths first } for (const dir of DEFAULT_LOAD_PATHS) { if (!loadPaths.has(dir)) { args.push(`--load-path=${dir}`); // then add default loadPaths (only if different) } } // Build the file. const execaOptions = { input: contents, // Adds the PATH param to the command so it can find local sass env: native ? undefined : npmRunPath.env(), extendEnv: native ? true : false, }; // If not using native them specify the project root so execa finds the right sass binary. if (!native && root) { // Prefer the node_modules/.bin execaOptions.preferLocal = true; // Specifies the local directory (which contains a .bin with sass) execaOptions.localDir = root; } const {stdout, stderr} = await execa('sass', args, execaOptions); // Handle the output. if (stderr) throw new Error(stderr); if (stdout) return stdout; }, }; }; ================================================ FILE: plugins/plugin-sass/test/__snapshots__/plugin.test.js.snap ================================================ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`plugin-sass returns the compiled Sass result: App.sass 1`] = ` ".child { text-align: left; } body { font-family: Helvetica, sans-serif; } .App { text-align: center; background: #333; }" `; exports[`plugin-sass returns the compiled Sass result: App.scss 1`] = ` "body { font-family: Helvetica, sans-serif; } .App { text-align: center; background: #333; }" `; ================================================ FILE: plugins/plugin-sass/test/fixtures/bad/bad.scss ================================================ awegagwagawingawignTHISISBADCODE ================================================ FILE: plugins/plugin-sass/test/fixtures/sass/App.sass ================================================ @use 'base' @use 'folder' body font-family: folder.$font-stack .App text-align: center background: base.$primary-color ================================================ FILE: plugins/plugin-sass/test/fixtures/sass/_base.sass ================================================ // _base.scss $primary-color: #333 ================================================ FILE: plugins/plugin-sass/test/fixtures/sass/folder/_child-partial.sass ================================================ .child text-align: left ================================================ FILE: plugins/plugin-sass/test/fixtures/sass/folder/_index.sass ================================================ // folder/_index.sass @use 'child-partial' $font-stack: Helvetica, sans-serif ================================================ FILE: plugins/plugin-sass/test/fixtures/scss/App.scss ================================================ @use 'base'; @use 'folder'; body { font-family: folder.$font-stack; } .App { text-align: center; background: base.$primary-color; } ================================================ FILE: plugins/plugin-sass/test/fixtures/scss/_base.scss ================================================ // _base.scss $primary-color: #333; ================================================ FILE: plugins/plugin-sass/test/fixtures/scss/folder/_index.scss ================================================ // folder/_index.scss $font-stack: Helvetica, sans-serif; ================================================ FILE: plugins/plugin-sass/test/plugin-mocked.test.js ================================================ /** * This test requires mocks which could disrupt other tests */ const path = require('path'); const mockExeca = jest.fn().mockImplementation(() => Promise.resolve({stdout: '', stderr: ''})); jest.mock('execa', () => (cmd, args) => mockExeca(cmd, args)); const plugin = require('../plugin'); const MOCK_CONFIG = null; const MOCK_LOAD = {filePath: path.join(__dirname, 'fixtures', 'scss', 'App.scss'), isDev: false}; const tests = [ {name: 'no options', given: {}, expect: []}, { name: 'string option', given: {compilerOptions: {style: 'compressed'}}, expect: [`--style=compressed`], }, { name: 'boolean option', given: {compilerOptions: {sourceMaps: false}}, expect: [`--no-source-maps`], }, { name: 'combination', given: {compilerOptions: {style: 'compressed', sourceMaps: true}}, expect: [`--style=compressed`, `--source-maps`], }, ]; describe('plugin-sass', () => { afterEach(() => { mockExeca.mockClear(); // clear calls between each test }); tests.forEach((t) => { it(t.name, async () => { const {load} = plugin(MOCK_CONFIG, t.given); await load(MOCK_LOAD); // Note: this test assumes execa is only used once in the entire plugin. // If execa needs to be used for another purpose, you can filter calls by 'sass' 1st param here t.expect.forEach((arg) => { expect(mockExeca.mock.calls[0][1]).toContain(arg); }); }); }); }); ================================================ FILE: plugins/plugin-sass/test/plugin.test.js ================================================ const plugin = require('../plugin.js'); const path = require('path'); const pathToSassApp = path.join(__dirname, 'fixtures/sass/App.sass'); const pathToSassBase = path.join(__dirname, 'fixtures/sass/_base.sass'); const pathToSassIndex = path.join(__dirname, 'fixtures/sass/folder/_index.sass'); const pathToSassChild = path.join(__dirname, 'fixtures/sass/folder/_child-partial.sass'); const pathToScssApp = path.join(__dirname, 'fixtures/scss/App.scss'); const pathToBadCode = path.join(__dirname, 'fixtures/bad/bad.scss'); describe('plugin-sass', () => { test('returns the compiled Sass result', async () => { const p = plugin(null, {}); const sassResult = await p.load({filePath: pathToSassApp, isDev: false}); expect(sassResult).toMatchSnapshot('App.sass'); const scssResult = await p.load({filePath: pathToScssApp, isDev: true}); expect(scssResult).toMatchSnapshot('App.scss'); }); test('returns undefined when a sass partial is loaded directly', async () => { const p = plugin(null, {}); const devResult = await p.load({filePath: pathToSassBase, isDev: false}); expect(devResult).toEqual(undefined); const prodResult = await p.load({filePath: pathToSassBase, isDev: true}); expect(prodResult).toEqual(undefined); }); test('throws an error when stderr output is returned', async () => { const p = plugin(null, {}); await expect(p.load({filePath: pathToBadCode, isDev: false})).rejects.toThrow( 'Command failed with exit code', ); }); test('marks a dependant as changed when an imported changes and isDev=true', async () => { const p = plugin(null, {}); p.markChanged = jest.fn(); await p.load({filePath: pathToSassApp, isDev: true}); expect(p.markChanged.mock.calls).toEqual([]); p.onChange({filePath: pathToSassApp}); expect(p.markChanged.mock.calls).toEqual([]); p.onChange({filePath: pathToSassBase}); expect(p.markChanged.mock.calls).toEqual([[pathToSassApp]]); p.markChanged.mockClear(); p.onChange({filePath: pathToSassIndex}); expect(p.markChanged.mock.calls).toEqual([[pathToSassApp]]); p.markChanged.mockClear(); p.onChange({filePath: pathToSassChild}); expect(p.markChanged.mock.calls).toEqual([[pathToSassApp]]); }); test('does not track dependant changes when isDev=false', async () => { const p = plugin(null, {}); p.markChanged = jest.fn(); await p.load({filePath: pathToSassApp, isDev: false}); p.onChange({filePath: pathToSassApp}); p.onChange({filePath: pathToSassBase}); expect(p.markChanged.mock.calls).toEqual([]); }); test('uses native sass CLI when native option = true', async () => { const p = plugin(null, {native: true}); process.env.PATH = ''; await expect(p.load({filePath: pathToSassApp, isDev: false})).rejects.toThrow( /(EPIPE|ENOENT|'sass' is not recognized as an internal or external command)/, ); }); }); ================================================ FILE: plugins/plugin-svelte/CHANGELOG.md ================================================ # @snowpack/plugin-svelte ## 3.7.0 ### Minor Changes - d9956f73: add explicit "mode" config (#3135) - 4403595e: Sourcemaps (#3271) ### Patch Changes - c9d2835c: chore: Update Node versions in GitHub CI (#3238) ## 3.6.1 ### Patch Changes - 56c5a231: fix typo in changelog - 40f02cf4: reorder changelog entries - 0694c5d3: [plugin-svelte] upgrade svelte-hmr to 0.13.2, fix remote source support (#2909) ## 3.6.0 ### Minor Changes - c27d7f48: feat(plugin-svelte): track preprocess dependencies ### Patch Changes - 5bf28731: fix bad svelte test - 4332623d: [ci] yarn format - 9ec250c4: fix merge conflicts - bea1c56c: Simplify. cleanup, enhance snowpack internals (#2707) - 49b29f97: [ci] yarn format ## 3.5.2 ### Patch Changes - b5b0590f: [ci] yarn format * ## 3.5.1 ### Patch Changes - 628e58a4: svelte plugin: make configurable hmrOptions - a8471965: [ci] yarn format - 9b465b6f: add backwards compat for the svelte plugin _For older releases, check our curated [release update thread](https://github.com/withastro/snowpack/discussions/1183) or the raw [commit history](https://github.com/withastro/snowpack/commits/main/plugins/plugin-svelte)._ ================================================ FILE: plugins/plugin-svelte/README.md ================================================ # @snowpack/plugin-svelte Use the [Svelte compiler](https://svelte.dev/docs#Compile_time) to build your `.svelte` files from source. Supports TypeScript and Sass out-of-the-box via [svelte-preprocess](https://github.com/sveltejs/svelte-preprocess). ``` npm install --save-dev @snowpack/plugin-svelte ``` ```js // snowpack.config.mjs export default { plugins: [ [ '@snowpack/plugin-svelte', { /* see optional “Plugin Options” below */ }, ], ], }; ``` ## Plugin Options By default, this plugin will look for a `svelte.config.js` file in your project directory to load `preprocess` and `compilerOptions` configuration from. However, you can also customize Svelte directly via the plugin options below. | Name | Type | Description | | :---------------- | :--------------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `configFilePath` | `string` | Relative path to a Svelte config file. Defaults to load `svelte.config.js` from the current project root directory. | | `input` | `string[]` | Array of file extensions to process. Uses `svelte.config.js` `extensions` if available. Defaults to `['.svelte']`. | | `preprocess` | [svelte.preprocess options](https://svelte.dev/docs#svelte_preprocess) | Configure the Svelte pre-processor. If this option is given, the config file `preprocess` option will be ignored. If any `preprocess` option is set to `false`, preprocessing will be skipped entirely regardless of file content. If no `preprocess` option is given, this plugin defaults to use [svelte-preprocess](https://github.com/sveltejs/svelte-preprocess). | | `compilerOptions` | [svelte.compile options](https://svelte.dev/docs#svelte_compile) | Configure the Svelte compiler.If this option is given, the config file `preprocess` option will be ignored. | | `hmrOptions` | [svelte-hmr options](https://github.com/rixo/svelte-hmr#options) | Configure HMR & "fast refresh" behavior for Svelte. | ================================================ FILE: plugins/plugin-svelte/package.json ================================================ { "name": "@snowpack/plugin-svelte", "version": "3.7.0", "main": "plugin.js", "license": "MIT", "homepage": "https://github.com/withastro/snowpack/tree/main/plugins/plugin-svelte#readme", "repository": { "type": "git", "url": "https://github.com/withastro/snowpack.git", "directory": "plugins/plugin-svelte" }, "publishConfig": { "access": "public" }, "peerDependencies": { "svelte": "^3.21.0" }, "gitHead": "a01616bb0787d56cd782f94cecf2daa12c7594e4", "dependencies": { "rollup-plugin-svelte": "^7.0.0", "svelte-hmr": "^0.13.2", "svelte-preprocess": "^4.7.2" } } ================================================ FILE: plugins/plugin-svelte/plugin.js ================================================ const svelte = require('svelte/compiler'); const svelteRollupPlugin = require('rollup-plugin-svelte'); const fs = require('fs'); const path = require('path'); const {createMakeHot} = require('svelte-hmr'); const inlineSourcemap = (code, map) => code + '\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,' + new Buffer(map.toString()).toString('base64'); module.exports = function plugin(snowpackConfig, pluginOptions = {}) { const isDev = snowpackConfig.mode !== 'production'; const useSourceMaps = snowpackConfig.buildOptions.sourcemap || snowpackConfig.buildOptions.sourceMaps; // Old Snowpack versions wouldn't build dependencies. Starting in v3.1, Snowpack's build pipeline // is run on all files, including npm package files. The rollup plugin is no longer needed. const importedByMap = new Map(); const needsRollupPlugin = typeof snowpackConfig.buildOptions.resolveProxyImports === 'undefined'; // Support importing Svelte files when you install dependencies. const packageOptions = snowpackConfig.packageOptions || snowpackConfig.installOptions; if (packageOptions.source === 'local') { if (needsRollupPlugin) { packageOptions.rollup = packageOptions.rollup || {}; packageOptions.rollup.plugins = packageOptions.rollup.plugins || []; packageOptions.rollup.plugins.push( svelteRollupPlugin({ include: /\.svelte$/, compilerOptions: {dev: isDev}, // Snowpack wraps JS-imported CSS in a JS wrapper, so use // Svelte's own first-class `emitCss: false` here. // TODO: Remove once Snowpack adds first-class CSS import support in deps. emitCss: false, }), ); } // Support importing sharable Svelte components. packageOptions.packageLookupFields.push('svelte'); } if ( pluginOptions.generate !== undefined || pluginOptions.dev !== undefined || pluginOptions.hydratable !== undefined || pluginOptions.css !== undefined || pluginOptions.preserveComments !== undefined || pluginOptions.preserveWhitespace !== undefined || pluginOptions.sveltePath !== undefined ) { throw new Error( `[plugin-svelte] Svelte.compile options moved to new config value: {compilerOptions: {...}}`, ); } if (pluginOptions.compileOptions !== undefined) { throw new Error( `[plugin-svelte] Could not recognize "compileOptions". Did you mean "compilerOptions"?`, ); } if (pluginOptions.input && !Array.isArray(pluginOptions.input)) { throw new Error(`[plugin-svelte] Option "input" must be an array (e.g. ['.svelte', '.svx'])`); } if (pluginOptions.input && pluginOptions.input.length === 0) { throw new Error(`[plugin-svelte] Option "input" must specify at least one filetype`); } let configFilePath = path.resolve( snowpackConfig.root || process.cwd(), pluginOptions.configFilePath || 'svelte.config.js', ); let compilerOptions = pluginOptions.compilerOptions; let preprocessOptions = pluginOptions.preprocess; let resolveInputOption = pluginOptions.input; const hmrOptions = pluginOptions.hmrOptions; if (fs.existsSync(configFilePath)) { const configFileConfig = require(configFilePath); preprocessOptions = preprocessOptions !== undefined ? preprocessOptions : configFileConfig.preprocess; compilerOptions = compilerOptions !== undefined ? compilerOptions : configFileConfig.compilerOptions; resolveInputOption = resolveInputOption !== undefined ? resolveInputOption : configFileConfig.extensions; } else { //user svelte.config.js is optional and should not error if not configured if (pluginOptions.configFilePath) { throw new Error(`[plugin-svelte] failed to find Svelte config file: "${configFilePath}"`); } } if (preprocessOptions === undefined) { preprocessOptions = require('svelte-preprocess')(); } function addImportsToMap(filePath, imp) { const importedBy = importedByMap.get(imp); if (importedBy) { importedBy.add(filePath); } else { importedByMap.set(imp, new Set([filePath])); } } let makeHot = (...args) => { makeHot = createMakeHot({ walk: svelte.walk, absoluteImports: false, versionNonAbsoluteImports: packageOptions.source === 'remote', }); return makeHot(...args); }; return { name: '@snowpack/plugin-svelte', resolve: { input: resolveInputOption || ['.svelte'], output: ['.js', '.css'], }, knownEntrypoints: [ 'svelte/internal', 'svelte-hmr/runtime/hot-api-esm.js', 'svelte-hmr/runtime/proxy-adapter-dom.js', ], /** * If any files imported the given file path, mark them as changed. * @private */ _markImportersAsChanged(filePath) { if (importedByMap.has(filePath)) { const importedBy = importedByMap.get(filePath); importedByMap.delete(filePath); for (const importerFilePath of importedBy) { this.markChanged(importerFilePath); } } }, /** * When a file changes, also mark it's importers as changed. * svelte.preprocess returns a list of preprocess deps - https://svelte.dev/docs#svelte_preprocess */ onChange({filePath}) { this._markImportersAsChanged(filePath); }, async load({filePath, isHmrEnabled, isSSR, isPackage}) { let dependencies = []; let codeToCompile = await fs.promises.readFile(filePath, 'utf-8'); // PRE-PROCESS if (preprocessOptions !== false) { ({code: codeToCompile, dependencies} = await svelte.preprocess( codeToCompile, preprocessOptions, { filename: filePath, }, )); } // in dev mode, track preprocess dependencies if (isDev && dependencies && dependencies.length) { dependencies.forEach((imp) => addImportsToMap(filePath, imp)); } const finalCompileOptions = { generate: isSSR ? 'ssr' : 'dom', css: isPackage ? true : false, ...compilerOptions, // Note(drew) should take precedence over generate above dev: isHmrEnabled || isDev, outputFilename: filePath, filename: filePath, }; const compiled = svelte.compile(codeToCompile, finalCompileOptions); const {js, css} = compiled; if (useSourceMaps) { js.code = inlineSourcemap(js.code, js.map); } const output = { '.js': { code: js.code, map: useSourceMaps ? js.map : undefined, }, }; if (isHmrEnabled && !isSSR) { output['.js'].code = makeHot({ id: filePath, compiledCode: js.code, hotOptions: { preserveLocalState: true, injectCss: true, ...hmrOptions, absoluteImports: false, noOverlay: true, }, compiled, originalCode: codeToCompile, compileOptions: finalCompileOptions, }); } if (!finalCompileOptions.css && css && css.code) { output['.css'] = { code: css.code, map: useSourceMaps ? css.map : undefined, }; } return output; }, }; }; ================================================ FILE: plugins/plugin-svelte/test/Button.svelte ================================================ ================================================ FILE: plugins/plugin-svelte/test/custom-config.js ================================================ module.exports = { preprocess: { __test: 'custom-config.js::preprocess', }, compilerOptions: { __test: 'custom-config.js', }, }; ================================================ FILE: plugins/plugin-svelte/test/plugin.test.js ================================================ const path = require('path'); const mockCompiler = jest.fn().mockImplementation((code) => ({js: {code}})); const mockPreprocessor = jest.fn().mockImplementation((code) => code); const mockPreprocessorWithDeps = jest.fn().mockImplementation((code) => ({ code, dependencies: ['path/to/file.stylus', 'path/to/file2.stylus'], })); let DEFAULT_CONFIG; const mockComponent = path.join(__dirname, 'Button.svelte'); beforeEach(() => { DEFAULT_CONFIG = { buildOptions: {sourceMaps: false}, packageOptions: { source: 'local', rollup: {plugins: []}, packageLookupFields: [], }, }; }); afterEach(() => { mockCompiler.mockClear(); mockPreprocessor.mockClear(); mockPreprocessorWithDeps.mockClear(); }); describe('@snowpack/plugin-svelte (mocked)', () => { jest.mock('svelte/compiler', () => ({compile: mockCompiler, preprocess: mockPreprocessor})); // important: mock before import const plugin = require('../plugin'); // TODO: safe to remove? afterEach(() => { mockCompiler.mockClear(); mockPreprocessor.mockClear(); }); afterAll(() => jest.resetModules()); it('logs error if config options set but finds no file', async () => { expect(() => { plugin(DEFAULT_CONFIG, { configFilePath: './plugins/plugin-svelte/this-file-does-not-exist.js', }); }).toThrow(/failed to find Svelte config file/); }); it('logs error if compileOptions is used instead of compilerOptions', async () => { expect(() => { plugin(DEFAULT_CONFIG, { compileOptions: {__test: 'ignore'}, }); }).toThrow( `[plugin-svelte] Could not recognize "compileOptions". Did you mean "compilerOptions"?`, ); }); it('logs error if old style config format is used', async () => { const badOptionCheck = /Svelte\.compile options moved to new config value/; expect(() => plugin(DEFAULT_CONFIG, { css: false, }), ).toThrow(badOptionCheck); expect(() => plugin(DEFAULT_CONFIG, { generate: 'dom', }), ).toThrow(badOptionCheck); }); it('logs error if resolve input is invalid', async () => { expect(() => { plugin(DEFAULT_CONFIG, { input: '.svelte', }); }).toThrow(`[plugin-svelte] Option "input" must be an array (e.g. ['.svelte', '.svx'])`); expect(() => { plugin(DEFAULT_CONFIG, { input: [], }); }).toThrow(`[plugin-svelte] Option "input" must specify at least one filetype`); }); it('passes compilerOptions to compiler', async () => { const compilerOptions = { __test: 'compilerOptions', }; const sveltePlugin = plugin(DEFAULT_CONFIG, {compilerOptions}); await sveltePlugin.load({filePath: mockComponent}); expect(mockCompiler.mock.calls[0][1]).toEqual({ __test: 'compilerOptions', css: false, dev: true, filename: mockComponent, generate: 'dom', outputFilename: mockComponent, }); }); it('passes preprocess options to compiler', async () => { const preprocess = {__test: 'preprocess'}; const sveltePlugin = plugin(DEFAULT_CONFIG, {preprocess}); await sveltePlugin.load({filePath: mockComponent}); expect(mockPreprocessor.mock.calls[0][1]).toEqual(preprocess); }); // For our users we load from the current working directory, but in jest that doesn't make sense it.skip('load config from a default svelte config file', async () => { const sveltePlugin = plugin(DEFAULT_CONFIG, {}); await sveltePlugin.load({filePath: mockComponent}); expect(mockCompiler.mock.calls[0][1]).toEqual({__test: 'svelte.config.js'}); expect(mockPreprocessor.mock.calls[0][1]).toEqual({__test: 'svelte.config.js::preprocess'}); }); it('load config from a custom svelte config file', async () => { const sveltePlugin = plugin(DEFAULT_CONFIG, { configFilePath: './plugins/plugin-svelte/test/custom-config.js', }); await sveltePlugin.load({filePath: mockComponent}); expect(mockCompiler.mock.calls[0][1]).toEqual({ __test: 'custom-config.js', css: false, dev: true, filename: mockComponent, generate: 'dom', outputFilename: mockComponent, }); expect(mockPreprocessor.mock.calls[0][1]).toEqual({__test: 'custom-config.js::preprocess'}); }); it('resolves custom file extensions', async () => { expect( plugin(DEFAULT_CONFIG, { input: ['.svelte', '.svx'], }).resolve.input, ).toMatchInlineSnapshot(` Array [ ".svelte", ".svx", ] `); expect( plugin(DEFAULT_CONFIG, { input: ['.svx'], }).resolve.input, ).toMatchInlineSnapshot(` Array [ ".svx", ] `); }); it('supports importing svelte components', async () => { const config = {...DEFAULT_CONFIG}; plugin(config, {}); expect(config.packageOptions.packageLookupFields).toEqual(['svelte']); config.packageOptions.packageLookupFields = ['module']; plugin(config, {}); expect(config.packageOptions.packageLookupFields).toEqual(['module', 'svelte']); }); }); describe('@snowpack/plugin-svelte (preprocessor deps)', () => { let plugin; beforeAll(() => { jest.mock('svelte/compiler', () => ({ compile: mockCompiler, preprocess: mockPreprocessorWithDeps, })); // important: mock before import plugin = require('../plugin'); }); afterAll(() => jest.resetModules()); it('marks a file as changed when a preprocess dependency changes', async () => { const preprocess = {__test: 'preprocess'}; const p = plugin(DEFAULT_CONFIG, {preprocess}); p.markChanged = jest.fn(); await p.load({filePath: mockComponent}); p.onChange({filePath: 'path/to/file.stylus'}); p.onChange({filePath: 'path/to/file2.stylus'}); expect(p.markChanged).toHaveBeenCalledTimes(2); expect(p.markChanged.mock.calls[0][0]).toBe(mockComponent); expect(p.markChanged.mock.calls[1][0]).toBe(mockComponent); }); }); ================================================ FILE: plugins/plugin-svelte/test/svelte.config.js ================================================ module.exports = { preprocess: { __test: 'svelte.config.js::preprocess', }, compilerOptions: { __test: 'svelte.config.js', }, }; ================================================ FILE: plugins/plugin-typescript/CHANGELOG.md ================================================ # @snowpack/plugin-typescript ## 1.2.1 ### Patch Changes - f8f32551: [ci] yarn format - 6b105339: add esinstall missing import hint (#2299) _For older releases, check our curated [release update thread](https://github.com/withastro/snowpack/discussions/1183) or the raw [commit history](https://github.com/withastro/snowpack/commits/main/plugins/plugin-typescript)._ ================================================ FILE: plugins/plugin-typescript/README.md ================================================ # @snowpack/plugin-typescript This plugin adds TypeScript type checking to any Snowpack project. When developing or building your site with Snowpack, this plugin will run TypeScript's `tsc` CLI in your project and pipe the output through Snowpack. Works with all version of TypeScript, as long as TypeScript is installed separately in your project. ## Usage ```bash npm i @snowpack/plugin-typescript typescript ``` Then add the plugin to your Snowpack config: ```js // snowpack.config.mjs export default { plugins: ['@snowpack/plugin-typescript'], }; ``` ## Plugin Options | Name | Type | Description | | :----- | :------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `tsc` | `string` | Optional custom tsc command. For example, you can use TypeScript compiler by specifying: `tsc: "tsc"`. | | `args` | `string` | Optional arguments to pass to the `tsc` CLI. For example, you can configure a custom project directory (with a custom `tsconfig.json` file) using `args: "--project ./your/custom/path"`. | ## A Note on Yarn v2 (PnP) TypeScript does not yet support PnP natively. The workaround is to replace the loading of the TypeScript plugin in `snowpack.config.mjs` with a call to [pnpify](https://yarnpkg.com/advanced/pnpify). ```js // See https://github.com/microsoft/TypeScript/issues/28289 // More info: https://medium.com/swlh/getting-started-with-yarn-2-and-typescript-43321a3acdee plugins: [['@snowpack/plugin-typescript', {tsc: 'yarn pnpify tsc'}]]; ``` ================================================ FILE: plugins/plugin-typescript/package.json ================================================ { "version": "1.2.1", "name": "@snowpack/plugin-typescript", "main": "plugin.js", "license": "MIT", "homepage": "https://github.com/withastro/snowpack/tree/main/plugins/plugin-typescript#readme", "repository": { "type": "git", "url": "https://github.com/withastro/snowpack.git", "directory": "plugins/plugin-typescript" }, "publishConfig": { "access": "public" }, "peerDependencies": { "typescript": "*" }, "dependencies": { "execa": "^5.1.1", "npm-run-path": "^4.0.1" } } ================================================ FILE: plugins/plugin-typescript/plugin.js ================================================ const execa = require('execa'); const npmRunPath = require('npm-run-path'); function typescriptPlugin(snowpackConfig, {tsc, args} = {}) { return { name: '@snowpack/plugin-typescript', async run({isDev, log}) { const workerPromise = execa.command( `${tsc ? tsc : 'tsc'} ${args ? args : ''} --noEmit ${isDev ? '--watch' : ''}`, { env: npmRunPath.env(), extendEnv: true, windowsHide: false, cwd: snowpackConfig.root || process.cwd(), }, ); const {stdout, stderr} = workerPromise; function dataListener(chunk) { let stdOutput = chunk.toString(); // In --watch mode, handle the "clear" character if (stdOutput.includes('\u001bc') || stdOutput.includes('\x1Bc')) { log('WORKER_RESET', {}); stdOutput = stdOutput.replace(/\x1Bc/, '').replace(/\u001bc/, ''); } log('WORKER_MSG', {msg: stdOutput}); } stdout && stdout.on('data', dataListener); stderr && stderr.on('data', dataListener); return workerPromise.catch((err) => { if (/ENOENT/.test(err.message)) { log('WORKER_MSG', { msg: 'WARN: "tsc" run failed. Is typescript installed in your project?', }); } throw err; }); }, }; } module.exports = typescriptPlugin; ================================================ FILE: plugins/plugin-typescript/test/plugin.test.js ================================================ const plugin = require('../plugin.js'); const {EventEmitter} = require('events'); jest.mock('execa'); const execa = require('execa'); describe('plugin-typescript', () => { let execaResult, execaFn; beforeEach(() => { execa.command.mockClear(); execaResult = { stderr: new EventEmitter(), stdout: new EventEmitter(), // Execa is weird, and returns a promise that also has other properties. Fake that here. catch: () => { return execaResult; }, }; execaFn = jest.fn().mockName('execa.command').mockReturnValue(execaResult); execa.command = execaFn; }); test('returns the execa command promise', async () => { const p = plugin({}); const result = await p.run({isDev: false, log: jest.fn}); expect(result).toEqual(execaResult); }); test('calls "tsc" correctly when isDev=false', async () => { const p = plugin({}); await p.run({isDev: false, log: jest.fn}); expect(execaFn.mock.calls[0][0]).toContain('--noEmit'); expect(execaFn.mock.calls[0][0]).not.toContain('--watch'); }); test('calls "tsc --watch" when isDev=true', async () => { const p = plugin({}); await p.run({isDev: true, log: jest.fn}); expect(execaFn.mock.calls[0][0]).toContain('--noEmit'); expect(execaFn.mock.calls[0][0]).toContain('--watch'); }); test('calls "tsc" correctly with args', async () => { const p = plugin({}, {args: '--foo bar'}); await p.run({isDev: false, log: jest.fn}); expect(execaFn.mock.calls[0][0]).toContain('--foo bar'); }); test('calls custom tsc command correctly with args', async () => { const p = plugin({}, {tsc: 'echo', args: 'Echo message'}); await p.run({isDev: false, log: jest.fn}); expect(execaFn.mock.calls[0][0]).toContain('Echo message'); }); test('handles tsc output', async () => { const logFn = jest.fn(); const p = plugin({}); await p.run({isDev: false, log: logFn}); execaResult.stdout.emit('data', Buffer.from('STDOUT_TEST_MESSAGE')); execaResult.stderr.emit('data', Buffer.from('STDERR_TEST_MESSAGE')); expect(logFn.mock.calls).toEqual([ ['WORKER_MSG', {msg: 'STDOUT_TEST_MESSAGE'}], ['WORKER_MSG', {msg: 'STDERR_TEST_MESSAGE'}], ]); }); test('handles tsc clear character messages', async () => { const logFn = jest.fn(); const p = plugin({}); await p.run({isDev: false, log: logFn}); execaResult.stderr.emit('data', Buffer.from('\u001bcTEST_CLEAR_MESSAGE')); execaResult.stderr.emit('data', Buffer.from('\x1BcTEST_CLEAR_MESSAGE')); expect(logFn.mock.calls).toEqual([ ['WORKER_RESET', {}], ['WORKER_MSG', {msg: 'TEST_CLEAR_MESSAGE'}], ['WORKER_RESET', {}], ['WORKER_MSG', {msg: 'TEST_CLEAR_MESSAGE'}], ]); }); }); ================================================ FILE: plugins/plugin-vue/CHANGELOG.md ================================================ # @snowpack/plugin-vue ## 2.6.2 ### Patch Changes - 7cf0e10d: [#3427] Add scopeId to exported components (#3481) ## 2.6.1 ### Patch Changes - 52a09b7d: Set the `preventAssignment` option in the vue plugin (#3422) ## 2.6.0 ### Minor Changes - 12410984: Enable proper tree-shaking of Vue's ESM bundler (#3382) ### Patch Changes - 1823b35f: Chore: update docs to refer to snowpack.config.mjs (#3341) ## 2.5.0 ### Minor Changes - 4403595e: Sourcemaps (#3271) ## 2.4.0 ### Minor Changes - 5d6ee1f3: Enable CSS Module support in Vue SSR (#3072) _For older releases, check our curated [release update thread](https://github.com/withastro/snowpack/discussions/1183) or the raw [commit history](https://github.com/withastro/snowpack/commits/main/plugins/plugin-vue)._ ================================================ FILE: plugins/plugin-vue/README.md ================================================ # @snowpack/plugin-vue Use the [Vue 3 compiler](https://www.npmjs.com/package/@vue/compiler-sfc) to build your `.vue` SFC files from source. ``` npm install --save-dev @snowpack/plugin-vue ``` ```js // snowpack.config.mjs export default { plugins: [ '@snowpack/plugin-vue', { /* see optional “Plugin Options” below */ }, ], }; ``` ## Plugin Options You may customize Vue's bundler behavior using the following plugin options. | Name | Type | Description | | :------------- | :-------: | :--------------------------------------------------------------------------------------------------- | | `optionsApi` | `boolean` | Enable/disable [Options API](https://v3.vuejs.org/api/options-api.html) support. Defaults to `true`. | | `prodDevtools` | `boolean` | Enable/disable devtools support in production. Defaults to `false`. | ================================================ FILE: plugins/plugin-vue/package.json ================================================ { "name": "@snowpack/plugin-vue", "version": "2.6.2", "main": "plugin.js", "license": "MIT", "homepage": "https://github.com/withastro/snowpack/tree/main/plugins/plugin-vue#readme", "repository": { "type": "git", "url": "https://github.com/withastro/snowpack.git", "directory": "plugins/plugin-vue" }, "publishConfig": { "access": "public" }, "dependencies": { "@rollup/plugin-replace": "^2.4.2", "@vue/compiler-sfc": "^3.0.10", "hash-sum": "^2.0.0" }, "devDependencies": { "vue": "*" }, "gitHead": "a01616bb0787d56cd782f94cecf2daa12c7594e4" } ================================================ FILE: plugins/plugin-vue/plugin-tsx-jsx.js ================================================ const fs = require('fs'); const scriptCompilers = require('./src/script-compilers'); module.exports = function plugin(snowpackConfig, pluginOptions) { return { name: '@snowpack/plugin-vue-tsx-jsx', resolve: { input: ['.tsx', '.jsx'], output: ['.js'], }, async load({filePath, fileExt}) { const content = fs.readFileSync(filePath, 'utf-8'); const lang = fileExt.slice(fileExt.lastIndexOf('.') + 1); const result = scriptCompilers.esbuildCompile(content, lang); return result; }, }; }; ================================================ FILE: plugins/plugin-vue/plugin.js ================================================ const fs = require('fs'); const path = require('path'); const hashsum = require('hash-sum'); const compiler = require('@vue/compiler-sfc'); const scriptCompilers = require('./src/script-compilers'); const replace = require('@rollup/plugin-replace'); const inlineSourcemap = (code, map) => code + '\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,' + new Buffer(map.toString()).toString('base64'); /** Friendly error display */ function displayError({contents, filePath, error}) { const pad = (number, pad) => `${Array.from(new Array(pad + 1)).join(' ')}${number}`; let output = [`${error.toString()}`, `[${filePath}]`]; if (error.loc) { output[1] += ` Line ${error.loc.start.line}, Column ${error.loc.start.column}`; const lineNo = (number) => ' ' + pad(number, (error.loc.end.line + 1).toString().length - number.toString().length) + ' | '; output.push(''); const allLines = ['', ...contents.split('\n')]; let currentLine = error.loc.start.line; output.push(lineNo(currentLine - 1) + allLines[currentLine - 1]); while (currentLine <= error.loc.end.line) { output.push(lineNo(currentLine) + allLines[currentLine]); currentLine++; } output.push( Array.from(new Array(error.loc.start.column + lineNo(currentLine - 1).length)).join(' ') + '^', ); output.push(lineNo(currentLine) + allLines[currentLine]); } return output.join('\n'); } module.exports = function plugin(snowpackConfig, pluginOptions = {}) { // Enable proper tree-shaking for Vue's ESM bundler // See http://link.vuejs.org/feature-flags const packageOptions = snowpackConfig.packageOptions || snowpackConfig.installOptions; if (packageOptions && packageOptions.source === 'local') { packageOptions.rollup = packageOptions.rollup || {}; packageOptions.rollup.plugins = packageOptions.rollup.plugins || []; const {optionsApi = true, prodDevtools = false} = pluginOptions; packageOptions.rollup.plugins.push( replace({ preventAssignment: false, values: { __VUE_OPTIONS_API__: JSON.stringify(optionsApi), __VUE_PROD_DEVTOOLS__: JSON.stringify(prodDevtools), }, }), ); } return { name: '@snowpack/plugin-vue', resolve: { input: ['.vue'], output: ['.js', '.css'], }, async load({filePath, isSSR}) { const {sourcemap, sourceMaps} = snowpackConfig.buildOptions; const id = hashsum(filePath); const contents = fs.readFileSync(filePath, 'utf-8'); const {descriptor, errors} = compiler.parse(contents, {filename: filePath}); // display errors if (errors && errors.length > 0) { throw new Error(displayError({error: errors[0], contents, filePath})); } const output = { '.js': {code: '', map: ''}, '.css': {code: '', map: ''}, }; if (descriptor.script) { const scriptLang = descriptor.script.lang; let scriptContent = descriptor.script.content; if (['jsx', 'ts', 'tsx'].includes(scriptLang)) { scriptContent = scriptCompilers.esbuildCompile(scriptContent, scriptLang); } if (['js', 'ts'].includes(scriptLang) || !scriptLang) { scriptContent = scriptContent.replace(`export default`, 'const defaultExport ='); } output['.js'].code += scriptContent; } else { output['.js'].code += `const defaultExport = {};\n`; } let hasCssModules = false; const cssModules = {}; await Promise.all( descriptor.styles.map(async (stylePart) => { // note: compileStyleAsync is required for SSR + CSS Modules const css = await compiler.compileStyleAsync({ filename: path.relative(snowpackConfig.root || process.cwd(), filePath), source: stylePart.content, id: `data-v-${id}`, scoped: stylePart.scoped != null, modules: stylePart.module != null, ssr: isSSR, preprocessLang: stylePart.lang, // preprocessCustomRequire: (id: string) => require(resolve(root, id)) // TODO load postcss config if present }); // gather CSS Module names if (stylePart.module) { hasCssModules = true; for (const [k, v] of Object.entries(css.modules)) { if (cssModules[k]) console.warn(`CSS Module name reused: ${k}`); cssModules[k] = v; } } if (css.errors && css.errors.length > 0) { console.error(JSON.stringify(css.errors)); } output['.css'].code += css.code; if ((sourcemap || sourceMaps) && css.map) output['.css'].map += JSON.stringify(css.map); }), ); if (descriptor.template) { const scoped = descriptor.styles.some((s) => s.scoped); const js = compiler.compileTemplate({ id, filename: path.relative(snowpackConfig.root || process.cwd(), filePath), source: descriptor.template.content, ssr: isSSR, ssrCssVars: [], preprocessLang: descriptor.template.lang, compilerOptions: { scopeId: scoped ? `data-v-${id}` : null, }, }); if (js.errors && js.errors.length > 0) { console.error(JSON.stringify(js.errors)); } const renderFn = isSSR ? `ssrRender` : `render`; if (output['.js'].code) output['.js'].code += '\n'; output['.js'].code += `${js.code.replace(/;?$/, ';')}`; // add trailing semicolon if missing (helps with some SSR cases) output['.js'].code += `\n\ndefaultExport.${renderFn} = ${renderFn};`; if (scoped) { output['.js'].code += `\n\ndefaultExport.__scopeId = "data-v-${id}";`; } // inject CSS Module styles, if needed if (hasCssModules) { // Note: this injection code is inspired by the official vue-loader for webpack and plays nicely with Vue. // But adjustments were needed to work with Snowpack. // See https://github.com/vuejs/vue-loader/blob/master/lib/codegen/styleInjection.js#L51 const styleInjectionCode = ` const cssModules = ${JSON.stringify(cssModules)}; Object.defineProperty(_ctx, '$style', { configurable: true, get: function() { return cssModules; } })\n`; output['.js'].code = output['.js'].code.replace( new RegExp(`(function ${renderFn}\\([^\n]+)`), `$1\n${styleInjectionCode}`, ); } output['.js'].code += `\n\nexport default defaultExport;`; if ((sourcemap || sourceMaps) && js.map) { output['.js'].code = inlineSourcemap(output['.js'].code, JSON.stringify(js.map)); output['.js'].map += JSON.stringify(js.map); } } // clean up if (!output['.js'].code) delete output['.js']; if (!output['.css'].code) delete output['.css']; return output; }, }; }; ================================================ FILE: plugins/plugin-vue/src/script-compilers.js ================================================ /* This license applies to parts of this file originating from the https://github.com/vitejs/vite repository: MIT License Copyright (c) 2019-present, Yuxi (Evan) You Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ const esbuild = require('esbuild'); const codeSnippetH = `import { Fragment } from 'vue';`; // https://github.com/vitejs/vite/blob/master/src/client/vueJsxCompat.ts const codeSnippetVueJsxCompat = `import {createVNode, isVNode} from 'vue'; const slice = Array.prototype.slice; export function jsx(tag, props = null, children = null) { if (arguments.length > 3 || isVNode(children)) { children = slice.call(arguments, 2); } return createVNode(tag, props, children); }`; /** * @param {string} content * @param {'jsx' | 'ts' | 'tsx'} lang */ const esbuildCompile = (content, lang) => { let result = ''; if (['jsx', 'tsx'].includes(lang)) { result += `${codeSnippetH}\n`; result += `${codeSnippetVueJsxCompat}\n`; } const {code} = esbuild.transformSync(content, { loader: lang, jsxFactory: 'jsx', jsxFragment: 'Fragment', }); result += `\n${code.trim()}\n`; return result.trim(); }; module.exports = { esbuildCompile, }; ================================================ FILE: plugins/plugin-vue/test/__snapshots__/plugin-tsx-jsx.test.js.snap ================================================ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`plugin with jsx 1`] = ` "import { Fragment } from 'vue'; import {createVNode, isVNode} from 'vue'; const slice = Array.prototype.slice; export function jsx(tag, props = null, children = null) { if (arguments.length > 3 || isVNode(children)) { children = slice.call(arguments, 2); } return createVNode(tag, props, children); } import {defineComponent} from \\"vue\\"; export default defineComponent({ name: \\"JsxContent\\", setup() { return () => /* @__PURE__ */ jsx(Fragment, null, /* @__PURE__ */ jsx(\\"h1\\", null, \\"JsxContent\\")); } });" `; exports[`plugin with tsx 1`] = ` "import { Fragment } from 'vue'; import {createVNode, isVNode} from 'vue'; const slice = Array.prototype.slice; export function jsx(tag, props = null, children = null) { if (arguments.length > 3 || isVNode(children)) { children = slice.call(arguments, 2); } return createVNode(tag, props, children); } import {defineComponent} from \\"vue\\"; export default defineComponent({ name: \\"TsxContent\\", setup() { const name = \\"TsxContent\\"; return () => /* @__PURE__ */ jsx(Fragment, null, /* @__PURE__ */ jsx(\\"h1\\", null, \\"TsxContent\\")); } });" `; ================================================ FILE: plugins/plugin-vue/test/__snapshots__/plugin-vue-ts-tsx-jsx.test.js.snap ================================================ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`plugin vue with jsx 1`] = ` Object { ".js": Object { "code": "import { Fragment } from 'vue'; import {createVNode, isVNode} from 'vue'; const slice = Array.prototype.slice; export function jsx(tag, props = null, children = null) { if (arguments.length > 3 || isVNode(children)) { children = slice.call(arguments, 2); } return createVNode(tag, props, children); } import {defineComponent} from \\"vue\\"; export default defineComponent({ name: \\"VueContentJsx\\", setup() { return () => /* @__PURE__ */ jsx(Fragment, null, /* @__PURE__ */ jsx(\\"h1\\", null, \\"VueContentJsx\\")); } });", "map": "", }, } `; exports[`plugin vue with ts 1`] = ` Object { ".js": Object { "code": "import {defineComponent} from \\"vue\\"; const defaultExport = defineComponent({ name: \\"VueContentTs\\", setup() { const name = \\"VueContentTs\\"; } }); import { openBlock as _openBlock, createBlock as _createBlock } from \\"vue\\" export function render(_ctx, _cache) { return (_openBlock(), _createBlock(\\"h1\\", null, \\"Vue Content Ts\\")) }; defaultExport.render = render; export default defaultExport;", "map": "", }, } `; exports[`plugin vue with tsx 1`] = ` Object { ".js": Object { "code": "import { Fragment } from 'vue'; import {createVNode, isVNode} from 'vue'; const slice = Array.prototype.slice; export function jsx(tag, props = null, children = null) { if (arguments.length > 3 || isVNode(children)) { children = slice.call(arguments, 2); } return createVNode(tag, props, children); } import {defineComponent} from \\"vue\\"; export default defineComponent({ name: \\"VueContentTsx\\", setup() { const name = \\"VueContentTsx\\"; return () => /* @__PURE__ */ jsx(Fragment, null, /* @__PURE__ */ jsx(\\"h1\\", null, \\"VueContentTsx\\")); } });", "map": "", }, } `; ================================================ FILE: plugins/plugin-vue/test/__snapshots__/plugin.test.js.snap ================================================ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`plugin base 1`] = ` Object { ".css": Object { "code": " .App { text-align: center; } .App-header { background-color: #f9f6f6; color: #32485f; min-height: 100vh; display: flex; flex-direction: column; align-items: center; justify-content: center; font-size: calc(10px + 2vmin); } .App-link { color: #00c185; } .App-logo { height: 40vmin; pointer-events: none; margin-bottom: 1rem; animation: App-logo-spin infinite 1.6s ease-in-out alternate; } @keyframes App-logo-spin { from { transform: scale(1); } to { transform: scale(1.06); } } ", "map": "", }, ".js": Object { "code": " const defaultExport = { data() { return { message: \\"Learn Vue\\" }; } }; import { createVNode as _createVNode, createTextVNode as _createTextVNode, toDisplayString as _toDisplayString, openBlock as _openBlock, createBlock as _createBlock } from \\"vue\\" const _hoisted_1 = { class: \\"App\\" } const _hoisted_2 = { class: \\"App-header\\" } const _hoisted_3 = /*#__PURE__*/_createVNode(\\"img\\", { src: \\"/logo.svg\\", class: \\"App-logo\\", alt: \\"logo\\" }, null, -1 /* HOISTED */) const _hoisted_4 = /*#__PURE__*/_createVNode(\\"p\\", null, [ /*#__PURE__*/_createTextVNode(\\" Edit \\"), /*#__PURE__*/_createVNode(\\"code\\", null, \\"src/App.vue\\"), /*#__PURE__*/_createTextVNode(\\" and save to reload. \\") ], -1 /* HOISTED */) const _hoisted_5 = { class: \\"App-link\\", href: \\"https://vuejs.org\\", target: \\"_blank\\", rel: \\"noopener noreferrer\\" } export function render(_ctx, _cache) { return (_openBlock(), _createBlock(\\"div\\", _hoisted_1, [ _createVNode(\\"header\\", _hoisted_2, [ _hoisted_3, _hoisted_4, _createVNode(\\"a\\", _hoisted_5, _toDisplayString(_ctx.message), 1 /* TEXT */) ]) ])) }; defaultExport.render = render; export default defaultExport;", "map": "", }, } `; exports[`plugin base only tpl 1`] = ` Object { ".js": Object { "code": "const defaultExport = {}; import { openBlock as _openBlock, createBlock as _createBlock } from \\"vue\\" export function render(_ctx, _cache) { return (_openBlock(), _createBlock(\\"h1\\", null, \\"Vue Content Only Tpl\\")) }; defaultExport.render = render; export default defaultExport;", "map": "", }, } `; exports[`plugin base style scoped 1`] = ` Object { ".css": Object { "code": " h1[data-v-XXXXXXXX] { color: red; } ", "map": "", }, ".js": Object { "code": " const defaultExport = {}; import { openBlock as _openBlock, createBlock as _createBlock, withScopeId as _withScopeId } from \\"vue\\" const _withId = /*#__PURE__*/_withScopeId(\\"data-v-XXXXXXXX\\") export const render = /*#__PURE__*/_withId((_ctx, _cache) => { return (_openBlock(), _createBlock(\\"h1\\", null, \\"Vue Content Style Scoped\\")) }); defaultExport.render = render; defaultExport.__scopeId = \\"data-v-XXXXXXXX\\"; export default defaultExport;", "map": "", }, } `; exports[`plugin base with sourceMap 1`] = ` Object { ".css": Object { "code": " .App { text-align: center; } .App-header { background-color: #f9f6f6; color: #32485f; min-height: 100vh; display: flex; flex-direction: column; align-items: center; justify-content: center; font-size: calc(10px + 2vmin); } .App-link { color: #00c185; } .App-logo { height: 40vmin; pointer-events: none; margin-bottom: 1rem; animation: App-logo-spin infinite 1.6s ease-in-out alternate; } @keyframes App-logo-spin { from { transform: scale(1); } to { transform: scale(1.06); } } ", "map": "", }, ".js": Object { "code": " const defaultExport = { data() { return { message: \\"Learn Vue\\" }; } }; import { createVNode as _createVNode, createTextVNode as _createTextVNode, toDisplayString as _toDisplayString, openBlock as _openBlock, createBlock as _createBlock } from \\"vue\\" const _hoisted_1 = { class: \\"App\\" } const _hoisted_2 = { class: \\"App-header\\" } const _hoisted_3 = /*#__PURE__*/_createVNode(\\"img\\", { src: \\"/logo.svg\\", class: \\"App-logo\\", alt: \\"logo\\" }, null, -1 /* HOISTED */) const _hoisted_4 = /*#__PURE__*/_createVNode(\\"p\\", null, [ /*#__PURE__*/_createTextVNode(\\" Edit \\"), /*#__PURE__*/_createVNode(\\"code\\", null, \\"src/App.vue\\"), /*#__PURE__*/_createTextVNode(\\" and save to reload. \\") ], -1 /* HOISTED */) const _hoisted_5 = { class: \\"App-link\\", href: \\"https://vuejs.org\\", target: \\"_blank\\", rel: \\"noopener noreferrer\\" } export function render(_ctx, _cache) { return (_openBlock(), _createBlock(\\"div\\", _hoisted_1, [ _createVNode(\\"header\\", _hoisted_2, [ _hoisted_3, _hoisted_4, _createVNode(\\"a\\", _hoisted_5, _toDisplayString(_ctx.message), 1 /* TEXT */) ]) ])) }; defaultExport.render = render; export default defaultExport;", "map": "", }, } `; ================================================ FILE: plugins/plugin-vue/test/__snapshots__/script-compilers.test.js.snap ================================================ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`esbuildCompile jsx 1`] = ` "import { Fragment } from 'vue'; import {createVNode, isVNode} from 'vue'; const slice = Array.prototype.slice; export function jsx(tag, props = null, children = null) { if (arguments.length > 3 || isVNode(children)) { children = slice.call(arguments, 2); } return createVNode(tag, props, children); } import {defineComponent} from \\"vue\\"; export default defineComponent({ name: \\"JsxContent\\", setup() { return () => /* @__PURE__ */ jsx(Fragment, null, /* @__PURE__ */ jsx(\\"h1\\", null, \\"JsxContent\\")); } });" `; exports[`esbuildCompile ts 1`] = ` "import {defineComponent} from \\"vue\\"; export default defineComponent({ name: \\"TsContent\\", setup() { const name = \\"TsContent\\"; } });" `; exports[`esbuildCompile tsx 1`] = ` "import { Fragment } from 'vue'; import {createVNode, isVNode} from 'vue'; const slice = Array.prototype.slice; export function jsx(tag, props = null, children = null) { if (arguments.length > 3 || isVNode(children)) { children = slice.call(arguments, 2); } return createVNode(tag, props, children); } import {defineComponent} from \\"vue\\"; export default defineComponent({ name: \\"TsxContent\\", setup() { const name = \\"TsxContent\\"; return () => /* @__PURE__ */ jsx(Fragment, null, /* @__PURE__ */ jsx(\\"h1\\", null, \\"TsxContent\\")); } });" `; ================================================ FILE: plugins/plugin-vue/test/plugin-tsx-jsx.test.js ================================================ const path = require('path'); const pluginTsxJsx = require('../plugin-tsx-jsx.js'); test('plugin with tsx', async () => { const pluginInstance = pluginTsxJsx({ buildOptions: { sourceMap: true, }, }); const pluginLoad = pluginInstance.load; const codeFilePath = path.resolve(__dirname, './stubs/TsxContent.tsx'); const resultContent = await pluginLoad({ filePath: codeFilePath, fileExt: '.tsx', }); expect(resultContent).toMatchSnapshot(); }); test('plugin with jsx', async () => { const pluginInstance = pluginTsxJsx({ buildOptions: { sourceMap: true, }, }); const pluginLoad = pluginInstance.load; const codeFilePath = path.resolve(__dirname, './stubs/JsxContent.jsx'); const resultContent = await pluginLoad({ filePath: codeFilePath, fileExt: '.jsx', }); expect(resultContent).toMatchSnapshot(); }); ================================================ FILE: plugins/plugin-vue/test/plugin-vue-ts-tsx-jsx.test.js ================================================ const path = require('path'); const plugin = require('../plugin'); test('plugin vue with ts', async () => { const pluginInstance = plugin({ buildOptions: { sourceMap: true, }, }); const pluginLoad = pluginInstance.load; const codeFilePath = path.resolve(__dirname, './stubs/VueContentTs.vue'); const resultContent = await pluginLoad({ filePath: codeFilePath, }); expect(resultContent).toMatchSnapshot(); }); test('plugin vue with tsx', async () => { const pluginInstance = plugin({ buildOptions: { sourceMap: true, }, }); const pluginLoad = pluginInstance.load; const codeFilePath = path.resolve(__dirname, './stubs/VueContentTsx.vue'); const resultContent = await pluginLoad({ filePath: codeFilePath, }); expect(resultContent).toMatchSnapshot(); }); test('plugin vue with jsx', async () => { const pluginInstance = plugin({ buildOptions: { sourceMap: true, }, }); const pluginLoad = pluginInstance.load; const codeFilePath = path.resolve(__dirname, './stubs/VueContentJsx.vue'); const resultContent = await pluginLoad({ filePath: codeFilePath, }); expect(resultContent).toMatchSnapshot(); }); ================================================ FILE: plugins/plugin-vue/test/plugin.test.js ================================================ const path = require('path'); const plugin = require('../plugin'); test('plugin base', async () => { const pluginInstance = plugin({ buildOptions: { sourceMap: false, }, }); const pluginLoad = pluginInstance.load; const codeFilePath = path.resolve(__dirname, './stubs/VueContent.vue'); const resultContent = await pluginLoad({ filePath: codeFilePath, }); expect(resultContent).toMatchSnapshot(); }); test('plugin base with sourceMap', async () => { const pluginInstance = plugin({ buildOptions: { sourceMap: true, }, }); const pluginLoad = pluginInstance.load; const codeFilePath = path.resolve(__dirname, './stubs/VueContent.vue'); const resultContent = await pluginLoad({ filePath: codeFilePath, }); expect(resultContent).toMatchSnapshot(); }); test('plugin base only tpl', async () => { const pluginInstance = plugin({ buildOptions: { sourceMap: true, }, }); const pluginLoad = pluginInstance.load; const codeFilePath = path.resolve(__dirname, './stubs/VueContentOnlyTpl.vue'); const resultContent = await pluginLoad({ filePath: codeFilePath, }); expect(resultContent).toMatchSnapshot(); }); test('plugin base style scoped', async () => { const pluginInstance = plugin({ buildOptions: { sourceMap: true, }, }); const pluginLoad = pluginInstance.load; const codeFilePath = path.resolve(__dirname, './stubs/VueContentStyleScoped.vue'); const resultContent = await pluginLoad({ filePath: codeFilePath, }); ['.css', '.js'].forEach((key) => { const code = resultContent[key].code; resultContent[key].code = code.replace(/data-v-[a-z0-9]+/g, 'data-v-XXXXXXXX'); }); expect(resultContent).toMatchSnapshot(); }); ================================================ FILE: plugins/plugin-vue/test/script-compilers.test.js ================================================ const fs = require('fs'); const path = require('path'); const scriptCompilers = require('../src/script-compilers'); test('esbuildCompile ts', () => { const {esbuildCompile} = scriptCompilers; const codeContent = fs.readFileSync(path.resolve(__dirname, './stubs/TsContent.ts')).toString(); const resultContent = esbuildCompile(codeContent, 'ts'); expect(resultContent).toMatchSnapshot(); }); test('esbuildCompile tsx', () => { const {esbuildCompile} = scriptCompilers; const codeContent = fs.readFileSync(path.resolve(__dirname, './stubs/TsxContent.tsx')).toString(); const resultContent = esbuildCompile(codeContent, 'tsx'); expect(resultContent).toMatchSnapshot(); }); test('esbuildCompile jsx', () => { const {esbuildCompile} = scriptCompilers; const codeContent = fs.readFileSync(path.resolve(__dirname, './stubs/JsxContent.jsx')).toString(); const resultContent = esbuildCompile(codeContent, 'jsx'); expect(resultContent).toMatchSnapshot(); }); ================================================ FILE: plugins/plugin-vue/test/stubs/JsxContent.jsx ================================================ import {defineComponent} from 'vue'; export default defineComponent({ name: 'JsxContent', setup() { return () => ( <>

JsxContent

); }, }); ================================================ FILE: plugins/plugin-vue/test/stubs/TsContent.ts ================================================ import {defineComponent} from 'vue'; export default defineComponent({ name: 'TsContent', setup() { const name: string = 'TsContent'; }, }); ================================================ FILE: plugins/plugin-vue/test/stubs/TsxContent.tsx ================================================ import {defineComponent} from 'vue'; export default defineComponent({ name: 'TsxContent', setup() { const name: string = 'TsxContent'; return () => ( <>

TsxContent

); }, }); ================================================ FILE: plugins/plugin-vue/test/stubs/VueContent.vue ================================================ ================================================ FILE: plugins/plugin-vue/test/stubs/VueContentJsx.vue ================================================ ================================================ FILE: plugins/plugin-vue/test/stubs/VueContentOnlyTpl.vue ================================================ ================================================ FILE: plugins/plugin-vue/test/stubs/VueContentStyleScoped.vue ================================================ ================================================ FILE: plugins/plugin-vue/test/stubs/VueContentTs.vue ================================================ ================================================ FILE: plugins/plugin-vue/test/stubs/VueContentTsx.vue ================================================ ================================================ FILE: plugins/plugin-vue/test/stubs/tsconfig.json ================================================ { "compilerOptions": { "jsx": "preserve", "jsxFactory": "h", "jsxFragmentFactory": "Fragment" } } ================================================ FILE: plugins/plugin-webpack/.gitignore ================================================ test/stubs/*_ignore/ ================================================ FILE: plugins/plugin-webpack/CHANGELOG.md ================================================ # @snowpack/plugin-webpack ## 3.0.0 ### Major Changes - c57b57a0: upgraded plugin-webpack to webpack 5 (#3154) ### Patch Changes - e93e5379: [ci] yarn format - 75c99b2f: use Webpack's output.path for emitted HTML (#3255) - 1823b35f: Chore: update docs to refer to snowpack.config.mjs (#3341) - 19b236df: [ci] yarn format - e9e8fb70: avoid conflicting JS entries when parsing HTML for Webpack (#3247) - e1bb3b89: [ci] yarn format - 9712f979: fix Webpack error formatting (#3256) - b0a1f206: Remove TSX components from app-template-vue-typescript (#3236) - 9091b598: [ci] yarn format - 9eb50368: update deps (#2928) - 56c5a231: fix typo in changelog ## 2.3.1 ## Patch Changes - f154a879: Refactor plugin-webpack tests (#2517) - 41c29f25: [ci] yarn format - 83616faa: Keep original HTML attributes on script tags (#2498) _For older releases, check our curated [release update thread](https://github.com/withastro/snowpack/discussions/1183) or the raw [commit history](https://github.com/withastro/snowpack/commits/main/plugins/plugin-webpack)._ ================================================ FILE: plugins/plugin-webpack/README.md ================================================ # @snowpack/plugin-webpack Use Webpack to bundle your Snowpack project for production when you run `snowpack build`. See our [build pipeline](https://www.snowpack.dev/concepts/build-pipeline) docs for more information. ### Install ``` npm install --save-dev @snowpack/plugin-webpack ``` Then add `@snowpack/plugin-webpack` to `snowpack.config.mjs`: ```js export default { plugins: [ [ '@snowpack/plugin-webpack', { /* see "Plugin Options" below */ }, ], ], }; ``` Once added to the configuration, `@snowpack/plugin-webpack` will run automatically on `snowpack build`. ### Plugin Options - `sourceMap: boolean` - Enable sourcemaps in the bundled output. - `outputPattern: {css: string, js: string, assets: string}` - Set the URL for your final bundled files. This is where they will be written to disk in the `build/` directory. See Webpack's [`output.filename`](https://webpack.js.org/configuration/output/#outputfilename) documentation for examples of valid values. - `extendConfig: (config: WebpackConfig) => WebpackConfig` - extend your webpack config, see below. - `manifest: boolean | string` - Enable generating a manifest file. The default value is `false`, the default file name is `./asset-manifest.json` if setting manifest to `true`. The relative path is resolved from the output directory. - `htmlMinifierOptions: boolean | object` - [See below](#minify-html). - `failOnWarnings: boolean` - Does fail the build when Webpack emits warnings. The default value is `false`. #### Extending The Default Webpack Config The `extendConfig` option is a function that you can provide to configure the default webpack config. If you provide this function, the plugin will pass its return value to `webpack.compile()`. Use this to make changes, add plugins, configure loaders, etc. ```js // snowpack.config.mjs export default { plugins: [ [ '@snowpack/plugin-webpack', { extendConfig: (config) => { config.plugins.push(/* ... */); return config; }, }, ], ], }; ``` #### Minify HTML With `htmlMinifierOptions` you can either disable the minification entirely or provide your own [options](https://github.com/kangax/html-minifier#options-quick-reference). ```js // snowpack.config.mjs export default { plugins: [ [ '@snowpack/plugin-webpack', { htmlMinifierOptions: false, // disabled entirely, }, ], ], }; ``` The default options are: ```js { collapseWhitespace: true, removeComments: true, removeEmptyAttributes: true, removeRedundantAttributes: true, removeScriptTypeAttributes: true, removeStyleLinkTypeAttributes: true, } ``` ================================================ FILE: plugins/plugin-webpack/package.json ================================================ { "name": "@snowpack/plugin-webpack", "version": "3.0.0", "main": "plugin.js", "license": "MIT", "homepage": "https://github.com/withastro/snowpack/tree/main/plugins/plugin-webpack#readme", "repository": { "type": "git", "url": "https://github.com/withastro/snowpack.git", "directory": "plugins/plugin-webpack" }, "publishConfig": { "access": "public" }, "dependencies": { "@babel/core": "^7.13.16", "@babel/preset-env": "^7.13.15", "babel-loader": "^8.1.0", "core-js": "^3.10.2", "css-loader": "^5.2.4", "css-minimizer-webpack-plugin": "^2.0.0", "glob": "^7.1.6", "html-minifier": "^4.0.0", "jsdom": "^16.5.3", "mini-css-extract-plugin": "^1.4.1", "webpack": "^5.34.0", "webpack-manifest-plugin": "^3.1.1" }, "devDependencies": { "fs-extra": "^9.1.0" } } ================================================ FILE: plugins/plugin-webpack/plugin.js ================================================ const crypto = require('crypto'); const fs = require('fs'); const glob = require('glob'); const path = require('path'); const util = require('util'); const url = require('url'); const webpack = require('webpack'); const MiniCssExtractPlugin = require('mini-css-extract-plugin'); const CssMinimizerPlugin = require('css-minimizer-webpack-plugin'); const {WebpackManifestPlugin} = require('webpack-manifest-plugin'); const jsdom = require('jsdom'); const {JSDOM} = jsdom; const minify = require('html-minifier').minify; function insertBefore(newNode, existingNode) { existingNode.parentNode.insertBefore(newNode, existingNode); } function ensureDirectoryExists(dir) { if (!fs.existsSync(dir)) { fs.mkdirSync(dir, {recursive: true}); } } function parseHTMLFiles({buildDirectory}) { // Get all html files from the output folder const pattern = buildDirectory + '/**/*.html'; const htmlFiles = glob.sync(pattern).map((htmlPath) => path.relative(buildDirectory, htmlPath)); const doms = {}; const jsEntries = {}; for (const htmlFile of htmlFiles) { const dom = new JSDOM(fs.readFileSync(path.join(buildDirectory, htmlFile))); //Find all local script, use it as the entrypoint const scripts = Array.from(dom.window.document.querySelectorAll('script')) .filter((el) => el.type.trim().toLowerCase() === 'module') .filter((el) => !/^[a-zA-Z]+:\/\//.test(el.src)); for (const el of scripts) { const src = el.src.trim(); const parsedPath = path.parse(src); // Using path + filename to avoid problems if files have the same name, i.e. // /index.js and /admin/index.js const name = path .join(parsedPath.dir, parsedPath.name) .replace(/\\/g, '/') // Paths other than the root will have a leading separator .replace(/^\//, ''); if (!(name in jsEntries)) { jsEntries[name] = { path: path.join(buildDirectory, src), occurrences: [], }; } jsEntries[name].occurrences.push({script: el, dom}); } doms[htmlFile] = dom; } return {doms, jsEntries}; } function emitHTMLFiles({doms, jsEntries, stats, baseUrl, outputDirectory, htmlMinifierOptions}) { const entrypoints = stats.toJson({assets: false, hash: true}).entrypoints; //Now that webpack is done, modify the html files to point to the newly compiled resources Object.keys(jsEntries).forEach((name) => { if (entrypoints[name] !== undefined && entrypoints[name]) { const assetFiles = entrypoints[name].assets || []; const jsFiles = assetFiles.filter((d) => d.name.endsWith('.js')); const cssFiles = assetFiles.filter((d) => d.name.endsWith('.css')); for (const occurrence of jsEntries[name].occurrences) { const originalScriptEl = occurrence.script; const dom = occurrence.dom; const head = dom.window.document.querySelector('head'); for (const jsFile of jsFiles) { // Clone node so we keep original attributes, and remove // `type=module` as that is not needed const scriptEl = originalScriptEl.cloneNode(); scriptEl.removeAttribute('type'); scriptEl.src = url.parse(baseUrl).protocol ? url.resolve(baseUrl, jsFile.name) : path.posix.join(baseUrl, jsFile.name); // insert _before_ so the relative order of these scripts is maintained insertBefore(scriptEl, originalScriptEl); } for (const cssFile of cssFiles) { const linkEl = dom.window.document.createElement('link'); linkEl.setAttribute('rel', 'stylesheet'); linkEl.href = url.parse(baseUrl).protocol ? url.resolve(baseUrl, cssFile.name) : path.posix.join(baseUrl, cssFile.name); head.append(linkEl); } originalScriptEl.remove(); } } }); //And write our modified html files out to the destination for (const [htmlFile, dom] of Object.entries(doms)) { const html = htmlMinifierOptions ? minify(dom.serialize(), htmlMinifierOptions) : dom.serialize(); const outputFile = path.join(outputDirectory, htmlFile); // If the user specified a different output, we may not have an existing folder structure ensureDirectoryExists(path.dirname(outputFile)); fs.writeFileSync(outputFile, html); } } function getSplitChunksConfig({numEntries}) { const isCss = (module) => module.type === `css/mini-extract`; /** * Implements a version of granular chunking, as described at https://web.dev/granular-chunking-nextjs/. */ return { chunks: 'all', maxInitialRequests: 25, minSize: 20000, cacheGroups: { default: false, vendors: false, /** * NPM libraries larger than 100KB are pulled into their own chunk * * We use a smaller cutoff than the reference implementation (which does 150KB), * because our babel-loader config compresses whitespace with `compact: true`. */ lib: { test(module) { return ( !isCss(module) && module.size() > 100000 && /_snowpack[/\\]pkg[/\\]/.test(module.identifier()) ); }, name(module) { /** * Name the chunk based on the filename in /pkg/*. * * E.g. /pkg/moment.js -> lib-moment.HASH.js */ const ident = module.libIdent({context: 'dir'}); const lastItem = ident .split('/') .reduceRight((item) => item) .replace(/\.js$/, ''); return `lib-${lastItem}`; }, priority: 30, minChunks: 1, reuseExistingChunk: true, }, // modules used by all entrypoints end up in commons commons: { test(module) { return !isCss(module); }, name: 'commons', // don't create a commons chunk until there are 2+ entries minChunks: Math.max(2, numEntries), priority: 20, }, // modules used by multiple chunks can be pulled into shared chunks shared: { test(module) { return !isCss(module); }, name(module, chunks) { const hash = crypto .createHash(`sha1`) .update(chunks.reduce((acc, chunk) => acc + chunk.name, ``)) .digest(`hex`); return hash; }, priority: 10, minChunks: 2, reuseExistingChunk: true, }, // Bundle all css & lazy css into one stylesheet to make sure lazy components do not break styles: { test(module) { return isCss(module); }, name: `styles`, priority: 40, enforce: true, }, }, }; } function getPresetEnvTargets({browserslist}) { if (Array.isArray(browserslist) || typeof browserslist === 'string') { return browserslist; } else if (typeof browserslist === 'object' && 'production' in browserslist) { return browserslist.production; } else { return '>0.75%, not ie 11, not UCAndroid >0, not OperaMini all'; } } module.exports = function plugin(config, args = {}) { // Deprecated: args.mode if (args.mode && args.mode !== 'production') { throw new Error('args.mode support has been removed.'); } // Validate: args.outputPattern args.outputPattern = args.outputPattern || {}; const jsOutputPattern = args.outputPattern.js || 'js/[name].[contenthash].js'; const cssOutputPattern = args.outputPattern.css || 'css/[name].[contenthash].css'; const assetsOutputPattern = args.outputPattern.assets || 'assets/[name].[contenthash][ext]'; if (!jsOutputPattern.endsWith('.js')) { throw new Error('Output Pattern for JS must end in .js'); } if (!cssOutputPattern.endsWith('.css')) { throw new Error('Output Pattern for CSS must end in .css'); } // Default options for HTMLMinifier // https://github.com/kangax/html-minifier#options-quick-reference const defaultHtmlMinifierOptions = { collapseWhitespace: true, removeComments: true, removeEmptyAttributes: true, removeRedundantAttributes: true, removeScriptTypeAttributes: true, removeStyleLinkTypeAttributes: true, }; const htmlMinifierOptions = args.htmlMinifierOptions === false ? false : Object.assign({}, defaultHtmlMinifierOptions, args.htmlMinifierOptions); const manifest = typeof args.manifest === 'string' ? args.manifest : !!args.manifest ? './asset-manifest.json' : undefined; // Webpack handles minification for us, so its safe to always // disable Snowpack's default minifier. config.buildOptions.minify = false; // Webpack creates unique file hashes for all generated bundles, // so we clean the build directory before building to remove outdated // build artifacts. config.buildOptions.clean = true; return { name: '@snowpack/plugin-webpack', async optimize({buildDirectory, log}) { const buildOptions = config.buildOptions || {}; let baseUrl = buildOptions.baseUrl || '/'; const tempBuildManifest = JSON.parse( await fs.readFileSync(path.join(config.root || process.cwd(), 'package.json'), { encoding: 'utf-8', }), ); const presetEnvTargets = getPresetEnvTargets(tempBuildManifest); let extendConfig = (cfg) => cfg; if (typeof args.extendConfig === 'function') { extendConfig = args.extendConfig; } else if (typeof args.extendConfig === 'object') { extendConfig = (cfg) => ({...cfg, ...args.extendConfig}); } const {doms, jsEntries} = parseHTMLFiles({buildDirectory}); if (Object.keys(jsEntries).length === 0) { throw new Error("Can't bundle without script tag in html"); } //Compile files using webpack let webpackConfig = { context: buildDirectory, resolve: { alias: { // TODO: Support a custom config.buildOptions.metaUrlPath '/_snowpack': path.join(buildDirectory, '_snowpack'), '/__snowpack__': path.join(buildDirectory, '__snowpack__'), '/web_modules': path.join(buildDirectory, 'web_modules'), }, }, module: { rules: [ { test: /\.js$/, exclude: /node_modules/, use: [ { loader: require.resolve('babel-loader'), options: { cwd: buildDirectory, configFile: false, babelrc: false, compact: true, presets: [ [ require.resolve('@babel/preset-env'), { targets: presetEnvTargets, bugfixes: true, modules: false, useBuiltIns: 'usage', corejs: 3, }, ], ], }, }, { loader: require.resolve('./plugins/import-meta-fix.js'), }, { loader: require.resolve('./plugins/proxy-import-resolve.js'), }, ], }, { test: /\.css$/, exclude: /\.module\.css$/, use: [ { loader: MiniCssExtractPlugin.loader, }, { loader: require.resolve('css-loader'), }, ], }, { test: /\.module\.css$/, use: [ { loader: MiniCssExtractPlugin.loader, }, { loader: require.resolve('css-loader'), options: { modules: true, }, }, ], }, { test: /.*/, exclude: [/\.js?$/, /\.json?$/, /\.css$/], // When using old assets loaders (i.e. file-loader/url-loader/raw-loader) // make sure to set 'javascript/auto' flag // https://webpack.js.org/guides/asset-modules/ type: 'asset/resource', generator: { filename: assetsOutputPattern, }, }, ], }, mode: 'production', devtool: args.sourceMap ? 'source-map' : undefined, optimization: { // extract webpack runtime to its own chunk: https://webpack.js.org/concepts/manifest/#runtime runtimeChunk: { name: `webpack-runtime`, }, splitChunks: getSplitChunksConfig({numEntries: Object.keys(jsEntries).length}), minimizer: [ `...`, // extends webpack internal ones (i.e. `terser-webpack-plugin`) new CssMinimizerPlugin({}), ], }, }; const plugins = [ //Extract a css file from imported css files new MiniCssExtractPlugin({ filename: cssOutputPattern, }), ]; if (manifest) { plugins.push(new WebpackManifestPlugin({fileName: manifest})); } let entry = {}; for (name in jsEntries) { entry[name] = jsEntries[name].path; } const extendedConfig = extendConfig({ ...webpackConfig, plugins, entry, output: { path: buildDirectory, publicPath: baseUrl, filename: jsOutputPattern, }, }); const compiler = webpack(extendedConfig); const stats = await new Promise((resolve, reject) => { compiler.run((err, stats) => { if (err) { reject(err); return; } const info = stats.toJson(extendedConfig.stats); if (stats.hasErrors()) { console.error( 'Webpack errors:\n' + info.errors.map((err) => err.message).join('\n-----\n'), ); reject(Error(`Webpack failed with ${info.errors} error(s).`)); return; } if (stats.hasWarnings()) { console.error( 'Webpack warnings:\n' + info.warnings.map((err) => err.message).join('\n-----\n'), ); if (args.failOnWarnings) { reject(Error(`Webpack failed with ${info.warnings} warnings(s).`)); return; } } resolve(stats); }); }); if (extendedConfig.stats !== 'none') { console.log( stats.toString( extendedConfig.stats ? extendedConfig.stats : { colors: true, all: false, assets: true, }, ), ); } // If the user specified a path, we need to put the HTML there too const outputDirectory = extendedConfig.output.path; emitHTMLFiles({ doms, jsEntries, stats, baseUrl, outputDirectory, htmlMinifierOptions, }); }, }; }; ================================================ FILE: plugins/plugin-webpack/plugins/import-meta-fix.js ================================================ /** MIT License Copyright (c) 2018 open-wc Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ const path = require('path'); const regex = /import\.meta/g; function toBrowserPath(filePath, _path = path) { return filePath.replace(new RegExp(_path.sep === '\\' ? '\\\\' : _path.sep, 'g'), '/'); } /** * Webpack loader to rewrite `import.meta` in modules. * * @example * return import.meta; * // becomes: return ({ url: `${window.location.protocol}//${window.location.host}/relative/path/to/file.js`, env: __SNOWPACK_ENV__ }); * * return import.meta.url; * // becomes: return ({ url: `${window.location.protocol}//${window.location.host}/relative/path/to/file.js`m env: __SNOWPACK_ENV__ }).url; */ module.exports = function (source) { const relativePath = this.context.substring( this.context.indexOf(this.rootContext) + this.rootContext.length + 1, this.resource.lastIndexOf(path.sep) + 1, ); const browserPath = toBrowserPath(relativePath); const fileName = this.resource.substring(this.resource.lastIndexOf(path.sep) + 1); let found = false; let rewrittenSource = source.replace(regex, () => { found = true; return `({ url: getAbsoluteUrl('${browserPath}/${fileName}'), env: __SNOWPACK_ENV__ })`; }); if (found) { return ` function getAbsoluteUrl(relativeUrl) { const publicPath = __webpack_public_path__; let url = ''; if (!publicPath || publicPath.indexOf('://') < 0) { url += window.location.protocol + '//' + window.location.host; } if (publicPath) { url += publicPath; } else { url += '/'; } return url + relativeUrl; } ${rewrittenSource}`; } else { return source; } }; ================================================ FILE: plugins/plugin-webpack/plugins/proxy-import-resolve.js ================================================ module.exports = function proxyImportResolver(source) { return source.replace( /(?:import|from)\s*['"].*\.(\w+)\.proxy\.js['"]/g, (fullMatch, originalExt) => { return fullMatch.replace('.proxy.js', ''); }, ); }; ================================================ FILE: plugins/plugin-webpack/test/__snapshots__/plugin.test.js.snap ================================================ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`@snowpack/plugin-webpack minimal - all options: files 1`] = ` Array [ Object { "content": "{ \\"index.js\\": \\"/index-826.js\\", \\"webpack-runtime.js\\": \\"/webpack-runtime-658.js\\", \\"styles.css\\": \\"/css/styles.XXXXXXXXXXXXXXXXXXXX.css\\", \\"assets/backpack.svg\\": \\"/assets/backpack.XXXXXXXXXXXXXXXXXXXX.svg\\", \\"index-826.js.map\\": \\"/index-826.js.map\\", \\"webpack-runtime-658.js.map\\": \\"/webpack-runtime-658.js.map\\", \\"styles.css.map\\": \\"/css/styles.XXXXXXXXXXXXXXXXXXXX.css.map\\" }", "filename": "asset-manifest.json", }, Object { "content": " ", "filename": "backpack.svg", }, Object { "content": "(self.webpackChunkplugin_webpack_test_minimal=self.webpackChunkplugin_webpack_test_minimal||[]).push([[826],{99:(s,e,t)=>{\\"use strict\\";t(933),console.log(\\"test\\")},933:(s,e,t)=>{\\"use strict\\";s.exports=t.p+\\"assets/backpack.XXXXXXXXXXXXXXXXXXXX.svg\\"}},s=>{\\"use strict\\";s.O(0,[532],(()=>(99,s(s.s=99)))),s.O()}]); //# sourceMappingURL=index-826.js.map", "filename": "index-826.js", }, Object { "content": "{\\"version\\":3,\\"sources\\":[\\"webpack://plugin-webpack-test-minimal/./index.js\\"],\\"names\\":[\\"console\\",\\"log\\"],\\"mappings\\":\\"8IAEAA,QAAQC,IAAI,S\\",\\"file\\":\\"index-826.js\\",\\"sourcesContent\\":[\\"import './styles.css';\\\\nimport './backpack.svg';\\\\nconsole.log('test');\\\\n\\"],\\"sourceRoot\\":\\"\\"}", "filename": "index-826.js.map", }, Object { "content": "Minimal test", "filename": "index.html", }, Object { "content": "import './styles.css'; import './backpack.svg'; console.log('test'); ", "filename": "index.js", }, Object { "content": "{ \\"name\\": \\"plugin-webpack-test-minimal\\", \\"main\\": \\"src/index.js\\" } ", "filename": "package.json", }, Object { "content": "body { background-color: skyblue; } ", "filename": "styles.css", }, Object { "content": "(()=>{\\"use strict\\";var r,e={},t={};function n(r){var a=t[r];if(void 0!==a)return a.exports;var l=t[r]={exports:{}};return e[r](l,l.exports,n),l.exports}n.m=e,r=[],n.O=(e,t,a,l)=>{if(!t){var o=1/0;for(s=0;s=l)&&Object.keys(n.O).every((r=>n.O[r](t[i])))?t.splice(i--,1):(p=!1,l0&&r[s-1][2]>l;s--)r[s]=r[s-1];r[s]=[t,a,l]},n.o=(r,e)=>Object.prototype.hasOwnProperty.call(r,e),n.p=\\"/\\",(()=>{var r={658:0,532:0};n.O.j=e=>0===r[e];var e=(e,t)=>{var a,l,[o,p,i]=t,s=0;for(a in p)n.o(p,a)&&(n.m[a]=p[a]);for(i&&i(n),e&&e(t);s {\\\\n\\\\tif(chunkIds) {\\\\n\\\\t\\\\tpriority = priority || 0;\\\\n\\\\t\\\\tfor(var i = deferred.length; i > 0 && deferred[i - 1][2] > priority; i--) deferred[i] = deferred[i - 1];\\\\n\\\\t\\\\tdeferred[i] = [chunkIds, fn, priority];\\\\n\\\\t\\\\treturn;\\\\n\\\\t}\\\\n\\\\tvar notFulfilled = Infinity;\\\\n\\\\tfor (var i = 0; i < deferred.length; i++) {\\\\n\\\\t\\\\tvar [chunkIds, fn, priority] = deferred[i];\\\\n\\\\t\\\\tvar fulfilled = true;\\\\n\\\\t\\\\tfor (var j = 0; j < chunkIds.length; j++) {\\\\n\\\\t\\\\t\\\\tif ((priority & 1 === 0 || notFulfilled >= priority) && Object.keys(__webpack_require__.O).every((key) => (__webpack_require__.O[key](chunkIds[j])))) {\\\\n\\\\t\\\\t\\\\t\\\\tchunkIds.splice(j--, 1);\\\\n\\\\t\\\\t\\\\t} else {\\\\n\\\\t\\\\t\\\\t\\\\tfulfilled = false;\\\\n\\\\t\\\\t\\\\t\\\\tif(priority < notFulfilled) notFulfilled = priority;\\\\n\\\\t\\\\t\\\\t}\\\\n\\\\t\\\\t}\\\\n\\\\t\\\\tif(fulfilled) {\\\\n\\\\t\\\\t\\\\tdeferred.splice(i--, 1)\\\\n\\\\t\\\\t\\\\tresult = fn();\\\\n\\\\t\\\\t}\\\\n\\\\t}\\\\n\\\\treturn result;\\\\n};\\",\\"// The module cache\\\\nvar __webpack_module_cache__ = {};\\\\n\\\\n// The require function\\\\nfunction __webpack_require__(moduleId) {\\\\n\\\\t// Check if module is in cache\\\\n\\\\tvar cachedModule = __webpack_module_cache__[moduleId];\\\\n\\\\tif (cachedModule !== undefined) {\\\\n\\\\t\\\\treturn cachedModule.exports;\\\\n\\\\t}\\\\n\\\\t// Create a new module (and put it into the cache)\\\\n\\\\tvar module = __webpack_module_cache__[moduleId] = {\\\\n\\\\t\\\\t// no module.id needed\\\\n\\\\t\\\\t// no module.loaded needed\\\\n\\\\t\\\\texports: {}\\\\n\\\\t};\\\\n\\\\n\\\\t// Execute the module function\\\\n\\\\t__webpack_modules__[moduleId](module, module.exports, __webpack_require__);\\\\n\\\\n\\\\t// Return the exports of the module\\\\n\\\\treturn module.exports;\\\\n}\\\\n\\\\n// expose the modules object (__webpack_modules__)\\\\n__webpack_require__.m = __webpack_modules__;\\\\n\\\\n\\",\\"__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))\\",\\"__webpack_require__.p = \\\\\\"/\\\\\\";\\",\\"// no baseURI\\\\n\\\\n// object to store loaded and loading chunks\\\\n// undefined = chunk not loaded, null = chunk preloaded/prefetched\\\\n// [resolve, reject, Promise] = chunk loading, 0 = chunk loaded\\\\nvar installedChunks = {\\\\n\\\\t658: 0,\\\\n\\\\t532: 0\\\\n};\\\\n\\\\n// no chunk on demand loading\\\\n\\\\n// no prefetching\\\\n\\\\n// no preloaded\\\\n\\\\n// no HMR\\\\n\\\\n// no HMR manifest\\\\n\\\\n__webpack_require__.O.j = (chunkId) => (installedChunks[chunkId] === 0);\\\\n\\\\n// install a JSONP callback for chunk loading\\\\nvar webpackJsonpCallback = (parentChunkLoadingFunction, data) => {\\\\n\\\\tvar [chunkIds, moreModules, runtime] = data;\\\\n\\\\t// add \\\\\\"moreModules\\\\\\" to the modules object,\\\\n\\\\t// then flag all \\\\\\"chunkIds\\\\\\" as loaded and fire callback\\\\n\\\\tvar moduleId, chunkId, i = 0;\\\\n\\\\tfor(moduleId in moreModules) {\\\\n\\\\t\\\\tif(__webpack_require__.o(moreModules, moduleId)) {\\\\n\\\\t\\\\t\\\\t__webpack_require__.m[moduleId] = moreModules[moduleId];\\\\n\\\\t\\\\t}\\\\n\\\\t}\\\\n\\\\tif(runtime) runtime(__webpack_require__);\\\\n\\\\tif(parentChunkLoadingFunction) parentChunkLoadingFunction(data);\\\\n\\\\tfor(;i < chunkIds.length; i++) {\\\\n\\\\t\\\\tchunkId = chunkIds[i];\\\\n\\\\t\\\\tif(__webpack_require__.o(installedChunks, chunkId) && installedChunks[chunkId]) {\\\\n\\\\t\\\\t\\\\tinstalledChunks[chunkId][0]();\\\\n\\\\t\\\\t}\\\\n\\\\t\\\\tinstalledChunks[chunkIds[i]] = 0;\\\\n\\\\t}\\\\n\\\\t__webpack_require__.O();\\\\n}\\\\n\\\\nvar chunkLoadingGlobal = self[\\\\\\"webpackChunkplugin_webpack_test_minimal\\\\\\"] = self[\\\\\\"webpackChunkplugin_webpack_test_minimal\\\\\\"] || [];\\\\nchunkLoadingGlobal.forEach(webpackJsonpCallback.bind(null, 0));\\\\nchunkLoadingGlobal.push = webpackJsonpCallback.bind(null, chunkLoadingGlobal.push.bind(chunkLoadingGlobal));\\"],\\"sourceRoot\\":\\"\\"}", "filename": "webpack-runtime-658.js.map", }, ] `; exports[`@snowpack/plugin-webpack minimal - no options: files 1`] = ` Array [ Object { "content": " ", "filename": "backpack.svg", }, Object { "content": "Minimal test", "filename": "index.html", }, Object { "content": "import './styles.css'; import './backpack.svg'; console.log('test'); ", "filename": "index.js", }, Object { "content": "{ \\"name\\": \\"plugin-webpack-test-minimal\\", \\"main\\": \\"src/index.js\\" } ", "filename": "package.json", }, Object { "content": "body { background-color: skyblue; } ", "filename": "styles.css", }, ] `; exports[`@snowpack/plugin-webpack respect extendedConfig.output.path: files 1`] = ` Array [ Object { "content": "Minimal test", "filename": "index.html", }, ] `; exports[`@snowpack/plugin-webpack multiple entrypoints w/ same filename: files 1`] = ` Array [ Object { "content": "Multiple entrypoints test - root", "filename": "index.html", }, Object { "content": "console.log('root test'); ", "filename": "index.js", }, Object { "content": "{ \\"name\\": \\"plugin-webpack-test-minimal\\", \\"main\\": \\"src/index.js\\" } ", "filename": "package.json", }, ] `; exports[`@snowpack/plugin-webpack multiple entrypoints w/ same filename: files 2`] = ` Array [ Object { "content": "Multiple entrypoints test - /admin", "filename": "index.html", }, Object { "content": "console.log('admin test'); ", "filename": "index.js", }, ] `; ================================================ FILE: plugins/plugin-webpack/test/plugin.test.js ================================================ const path = require('path'); const fs = require('fs-extra'); const plugin = require('../plugin'); const readFilesSync = require('./readFilesSync'); const STUBS_DIR = path.join(__dirname, 'stubs/minimal/'); const IGNORED_STUBS_DIR = path.join(__dirname, 'stubs/minimal_ignore/'); const MULTIPLE_DIR = path.join(__dirname, 'stubs/multiple-entrypoints/'); const IGNORED_MULTIPLE_DIR = path.join(__dirname, 'stubs/multiple-entrypoints_ignore/'); describe('@snowpack/plugin-webpack', () => { // Copy over the stub folder to an git-ignored path and mock console.log beforeEach(() => { if (fs.existsSync(IGNORED_STUBS_DIR)) fs.removeSync(IGNORED_STUBS_DIR); fs.copySync(STUBS_DIR, IGNORED_STUBS_DIR); if (fs.existsSync(IGNORED_MULTIPLE_DIR)) fs.removeSync(IGNORED_MULTIPLE_DIR); fs.copySync(MULTIPLE_DIR, IGNORED_MULTIPLE_DIR); }); afterAll(() => { if (fs.existsSync(IGNORED_STUBS_DIR)) fs.removeSync(IGNORED_STUBS_DIR); if (fs.existsSync(IGNORED_MULTIPLE_DIR)) fs.removeSync(IGNORED_MULTIPLE_DIR); }); it('minimal - no options', async () => { const pluginInstance = plugin({ buildOptions: {}, }); await pluginInstance.optimize({ buildDirectory: IGNORED_STUBS_DIR, }); expect(readFilesSync(IGNORED_STUBS_DIR)).toMatchSnapshot('files'); }); it('minimal - all options', async () => { const pluginInstance = plugin( { buildOptions: {}, }, { sourceMap: true, outputPattern: { js: '[name]-[id].js', }, extendConfig: (config) => config, manifest: true, htmlMinifierOptions: true, }, ); await pluginInstance.optimize({ buildDirectory: IGNORED_STUBS_DIR, }); expect(readFilesSync(IGNORED_STUBS_DIR)).toMatchSnapshot('files'); }); it('respect extendedConfig.output.path', async () => { const pluginInstance = plugin( { buildOptions: {}, }, { extendConfig: (config) => { config.output.path = path.resolve(IGNORED_STUBS_DIR, 'dist'); return config; }, }, ); await pluginInstance.optimize({ buildDirectory: IGNORED_STUBS_DIR, }); expect(readFilesSync(path.join(IGNORED_STUBS_DIR, 'dist'))).toMatchSnapshot('files'); expect(fs.existsSync(path.join(IGNORED_STUBS_DIR, 'js'))).toBe(false); expect(fs.existsSync(path.join(IGNORED_STUBS_DIR, 'css'))).toBe(false); expect(fs.existsSync(path.join(IGNORED_STUBS_DIR, 'assets'))).toBe(false); }); it('multiple entrypoints w/ same filename', async () => { const pluginInstance = plugin({ buildOptions: {}, }); await pluginInstance.optimize({ buildDirectory: IGNORED_MULTIPLE_DIR, }); const rootHtml = fs.readFileSync(path.join(IGNORED_MULTIPLE_DIR, 'index.html'), { encoding: 'utf8', }); const adminHtml = fs.readFileSync(path.join(IGNORED_MULTIPLE_DIR, 'admin/index.html'), { encoding: 'utf8', }); const rootScripts = rootHtml.match(/ ================================================ FILE: plugins/plugin-webpack/test/stubs/minimal/index.js ================================================ import './styles.css'; import './backpack.svg'; console.log('test'); ================================================ FILE: plugins/plugin-webpack/test/stubs/minimal/package.json ================================================ { "name": "plugin-webpack-test-minimal", "main": "src/index.js" } ================================================ FILE: plugins/plugin-webpack/test/stubs/minimal/styles.css ================================================ body { background-color: skyblue; } ================================================ FILE: plugins/plugin-webpack/test/stubs/multiple-entrypoints/admin/index.html ================================================ Multiple entrypoints test - /admin ================================================ FILE: plugins/plugin-webpack/test/stubs/multiple-entrypoints/admin/index.js ================================================ console.log('admin test'); ================================================ FILE: plugins/plugin-webpack/test/stubs/multiple-entrypoints/index.html ================================================ Multiple entrypoints test - root ================================================ FILE: plugins/plugin-webpack/test/stubs/multiple-entrypoints/index.js ================================================ console.log('root test'); ================================================ FILE: plugins/plugin-webpack/test/stubs/multiple-entrypoints/package.json ================================================ { "name": "plugin-webpack-test-minimal", "main": "src/index.js" } ================================================ FILE: plugins/web-test-runner-plugin/CHANGELOG.md ================================================ # @snowpack/web-test-runner-plugin ## 0.2.2 ### Patch Changes - 50987423: Add "repository" and "homepage" listings (#2846) - bea1c56c: Simplify. cleanup, enhance snowpack internals (#2707) - a8a30339: [ci] yarn format - 25565308: Allow 404s in web-test-runner-plugin (#2546) - 7a79de95: fix bad snowpackconfig reference - a65023e2: [ci] yarn format ## 0.2.1 ### Patch Changes - cc36acf2: Update @web/test-runner (#2222) _For older releases, check our curated [release update thread](https://github.com/withastro/snowpack/discussions/1183) or the raw [commit history](https://github.com/withastro/snowpack/commits/main/plugins/web-test-runner-plugin)._ ================================================ FILE: plugins/web-test-runner-plugin/README.md ================================================ # @snowpack/web-test-runner-plugin A [@web/test-runner](https://modern-web.dev/docs/test-runner/overview/) plugin to test Snowpack-powered projects. This plugin automatically connects to the Snowpack project in the current directory, loads the project configuration, and the uses your already-configured Snowpack build pipeline to build each test file. ## Usage ``` npm install @snowpack/web-test-runner-plugin --save-dev ``` ``` // web-test-runner.config.js module.exports = { plugins: [require('@snowpack/web-test-runner-plugin')()], }; ``` ## Options None! If you need to configure Snowpack, you can do so in your project `snowpack.config.mjs` file. Looking for support for some missing option/configuration? Please file an isuse! Your feedback is important. ================================================ FILE: plugins/web-test-runner-plugin/package.json ================================================ { "name": "@snowpack/web-test-runner-plugin", "version": "0.2.2", "main": "plugin.js", "license": "MIT", "publishConfig": { "access": "public" }, "repository": { "type": "git", "url": "https://github.com/withastro/snowpack.git", "directory": "plugins/web-test-runner-plugin" }, "homepage": "https://www.snowpack.dev/guides/web-test-runner", "peerDependencies": { "@web/test-runner": ">=0.10.0 <1.0.0", "snowpack": "^3.8.0" }, "devDependencies": { "@web/test-runner": ">=0.11.0" }, "gitHead": "a01616bb0787d56cd782f94cecf2daa12c7594e4" } ================================================ FILE: plugins/web-test-runner-plugin/plugin.js ================================================ const {isTestFilePath} = require('@web/test-runner'); const snowpack = require('snowpack'); const path = require('path'); /** * Checks whether the url is a virtual file served by @web/test-runner. * @param {string} url */ function isTestRunnerFile(url) { return url.startsWith('/__web-dev-server') || url.startsWith('/__web-test-runner'); } module.exports = function () { let server, config; return { name: 'snowpack-plugin', async serverStart({fileWatcher}) { config = await snowpack.loadConfiguration({ mode: 'test', packageOptions: {external: ['/__web-dev-server__web-socket.js']}, devOptions: {open: 'none', output: 'stream', hmr: false}, }); // npm packages should be installed/prepared ahead of time. console.log('[snowpack] starting server...'); fileWatcher.add(Object.keys(config.mount)); server = await snowpack.startServer({ config, lockfile: null, }); }, async serverStop() { return server.shutdown(); }, async serve({request}) { if (isTestRunnerFile(request.url)) { return; } const reqPath = request.path; try { const result = await server.loadUrl(reqPath, {isSSR: false}); return {body: result.contents, type: result.contentType}; } catch { return; } }, transformImport({source}) { if (!isTestFilePath(source) || isTestRunnerFile(source)) { return; } // PERF(fks): https://github.com/withastro/snowpack/pull/1259/files#r502963818 const reqPath = source.substring( 0, source.indexOf('?') === -1 ? undefined : source.indexOf('?'), ); const sourcePath = path.join(config.root || process.cwd(), reqPath); try { return snowpack.getUrlForFile(sourcePath, config); } catch { return; } }, }; }; ================================================ FILE: scripts/release-all.js ================================================ // How to release many packages at once: // 1. get a list of what everything is meant to be going out (git diff [LAST RELEASE COMMIT] --stat) // 2. create the release-all.js script (get the patch vs. minor vs. major for each package going out. ex: git diff [LAST RELEASE COMMIT] plugins/plugin-svelte) // 3. run `yarn build && yarn bundle` // 4. run `../release-all.js` (release-all script must be copied outside of the working directory, so that we pass the "clean working directory" check) const release = require('./release.cjs'); release('esinstall', 'latest', true); release('skypack', 'latest', true); release('snowpack', 'latest', true); release('app-template-blank-typescript', 'latest', true); release('app-template-lit-element', 'latest', true); release('app-template-preact-typescript', 'latest', true); release('app-template-react-typescript', 'latest', true); release('app-template-svelte-typescript', 'latest', true); release('app-template-vue-typescript', 'latest', true); release('plugins/plugin-optimize', 'latest', true); release('plugins/web-test-runner-plugin', 'latest', true); ================================================ FILE: scripts/release.cjs ================================================ const fs = require('fs'); const path = require('path'); const execa = require('execa'); function formatDate() { var d = new Date(), month = '' + (d.getMonth() + 1), day = '' + d.getDate(), year = d.getFullYear(); if (month.length < 2) month = '0' + month; if (day.length < 2) day = '0' + day; return [year, month, day].join('-'); } module.exports = function release(pkgFolder, tag, skipBuild) { console.log(`# release(${pkgFolder}, ${tag})`); const root = path.resolve(__dirname, '..'); const dir = path.resolve(root, pkgFolder); if (execa.sync('git', ['status', '--porcelain'], {cwd: dir}).stdout) { console.error('working directory not clean!'); process.exit(1); } if (skipBuild !== true) { console.log('Building...'); console.log(execa.sync('yarn', ['run', 'build'], {cwd: root})); } console.log('Publishing...'); const pkgJsonLoc = path.join(dir, 'package.json'); if (!pkgFolder.startsWith('create-snowpack-app/')) { execa.sync('yarn', ['changeset', 'version']); } const {name: pkgName, version: newPkgVersion} = JSON.parse(fs.readFileSync(pkgJsonLoc, 'utf8')); const newPkgTag = `${pkgName}@${newPkgVersion}`; console.log(execa.sync('git', ['add', '-A'], {cwd: dir})); console.log(execa.sync('git', ['commit', '--allow-empty', '-m', `[skip ci] ${newPkgTag}`], {cwd: dir})); console.log(execa.sync('git', ['tag', newPkgTag], {cwd: dir})); if (pkgName === 'snowpack') console.log(execa.sync('git', ['tag', `v${newPkgVersion}`], {cwd: dir})); // 'snowpack' only: also tag as vX.X.X (for GitHub releases) console.log(execa.sync('npm', ['publish', '--tag', tag], {cwd: dir})); // Only push to github on latest release, since a pre-release will break // yarns ability to link workspaces (ex: ^3.0.0 doesn't match 3.1.0-pre.1). if (tag === 'latest') { console.log(execa.sync('git', ['push', 'origin', 'main'], {cwd: dir})); console.log(execa.sync('git', ['push', '--tags'], {cwd: dir})); } }; ================================================ FILE: skypack/.gitignore ================================================ node_modules lib ================================================ FILE: skypack/.prettierrc ================================================ { "singleQuote": true, "trailingComma": "all", "bracketSpacing": false, "printWidth": 100 } ================================================ FILE: skypack/CHANGELOG.md ================================================ # skypack ## 0.3.3 ### Patch Changes - 5e84cdd8: Fixes a ghost dependency on 'tar' ## 0.3.2 ### Patch Changes - b5e49467: update changelogs - bf220191: [ci] yarn format ## 0.3.1 ### Patch Changes - 59ee847e: cleanup broken skypack sdk references ================================================ FILE: skypack/LICENSE ================================================ MIT License Copyright (c) 2019 Fred K. Schott Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: skypack/README.md ================================================ # Skypack This is an experimental SDK for interacting with the Skypack Package CDN. It is not yet ready for public use, and may change without notice. Use at your own risk! More documentation will be written as this gets closer to a public release! Until then, you can browse the package code to get a better idea of the interface. ``` npm install skypack ``` ================================================ FILE: skypack/index.esm.mjs ================================================ import Pkg from './lib/index.js'; export const rollupPluginSkypack = Pkg.rollupPluginSkypack; export const generateImportMap = Pkg.generateImportMap; export const fetchCDN = Pkg.fetchCDN; export const buildNewPackage = Pkg.buildNewPackage; export const lookupBySpecifier = Pkg.lookupBySpecifier; export const clearCache = Pkg.clearCache; export const SKYPACK_ORIGIN = Pkg.SKYPACK_ORIGIN; ================================================ FILE: skypack/package.json ================================================ { "name": "skypack", "version": "0.3.3", "description": "The Skypack SDK.", "keywords": [ "skypack", "esm", "cdn", "sdk" ], "author": "Fred K. Schott ", "license": "MIT", "publishConfig": { "access": "public" }, "repository": { "type": "git", "url": "https://github.com/withastro/snowpack.git", "directory": "skypack" }, "scripts": { "build": "tsc --noUnusedLocals false --noUnusedParameters false", "build:watch": "tsc --watch --noUnusedLocals false --noUnusedParameters false", "lint": "tsc --noEmit && package-check" }, "engines": { "node": ">=10.19.0" }, "main": "lib/index.js", "types": "lib/index.d.ts", "exports": { ".": { "import": "./index.esm.mjs", "require": "./lib/index.js" }, "./package.json": "./package.json" }, "files": [ "lib", "index.bin.js", "index.esm.mjs" ], "dependencies": { "cacache": "^15.0.0", "cachedir": "^2.3.0", "esinstall": "^1.0.0", "etag": "^1.8.1", "find-up": "^5.0.0", "got": "^11.1.4", "kleur": "^4.1.0", "mkdirp": "^1.0.3", "p-queue": "^6.2.1", "rimraf": "^3.0.0", "rollup": "~2.37.1", "tar": "^6.1.0", "validate-npm-package-name": "^3.0.0" } } ================================================ FILE: skypack/src/index.ts ================================================ import mkdirp from 'mkdirp'; import cacache from 'cacache'; import path from 'path'; import tar from 'tar'; import fs from 'fs'; import url from 'url'; import got, {Response} from 'got'; import {IncomingHttpHeaders} from 'http'; import {ImportMap, RESOURCE_CACHE, HAS_CDN_HASH_REGEX} from './util'; export {rollupPluginSkypack} from './rollup-plugin-remote-cdn'; export const SKYPACK_ORIGIN = 'https://cdn.skypack.dev'; function parseRawPackageImport(spec: string): [string, string | null] { const impParts = spec.split('/'); if (spec.startsWith('@')) { const [scope, name, ...rest] = impParts; return [`${scope}/${name}`, rest.join('/') || null]; } const [name, ...rest] = impParts; return [name, rest.join('/') || null]; } export class SkypackSDK { origin: string; constructor(options: {origin?: string} = {}) { this.origin = options.origin || SKYPACK_ORIGIN; } async generateImportMap( webDependencies: Record, inheritFromImportMap?: ImportMap, ): Promise { const newLockfile: ImportMap = inheritFromImportMap ? {imports: {...inheritFromImportMap.imports}} : {imports: {}}; await Promise.all( Object.entries(webDependencies).map(async ([packageName, packageSemver]) => { if (packageSemver === null) { delete newLockfile.imports[packageName]; delete newLockfile.imports[packageName + '/']; return; } const lookupResponse = await this.lookupBySpecifier(packageName, packageSemver); if (lookupResponse.error) { throw lookupResponse.error; } if (lookupResponse.pinnedUrl) { let keepGoing = true; const deepPinnedUrlParts = lookupResponse.pinnedUrl.split('/'); // TODO: Get ?meta support to get this info via JSON instead of header manipulation deepPinnedUrlParts.shift(); // remove "" deepPinnedUrlParts.shift(); // remove "pin" while (keepGoing) { const investigate = deepPinnedUrlParts.pop()!; if (HAS_CDN_HASH_REGEX.test(investigate)) { keepGoing = false; deepPinnedUrlParts.push(investigate); } } newLockfile.imports[packageName] = this.origin + '/' + deepPinnedUrlParts.join('/'); newLockfile.imports[packageName + '/'] = this.origin + '/' + deepPinnedUrlParts.join('/') + '/'; } }), ); const newLockfileSorted = Object.keys(newLockfile.imports).sort((a, b) => { // We want 'xxx/' to come after 'xxx', so we convert it to a space (the character with the highest sort order) // See: http://support.ecisolutions.com/doc-ddms/help/reportsmenu/ascii_sort_order_chart.htm return a.replace(/\/$/, ' ').localeCompare(b.replace(/\/$/, ' ')); }); return { imports: newLockfileSorted.reduce((prev, k) => { prev[k] = newLockfile.imports[k]; return prev; }, {}), }; } async fetch( resourceUrl: string, userAgent?: string, ): Promise<{ body: Buffer; headers: IncomingHttpHeaders; statusCode: number; isCached: boolean; isStale: boolean; }> { if (!resourceUrl.startsWith(this.origin)) { resourceUrl = this.origin + resourceUrl; } const cachedResult = await cacache.get(RESOURCE_CACHE, resourceUrl).catch(() => null); if (cachedResult) { const cachedResultMetadata = cachedResult.metadata as ResourceCacheMetadata; const freshUntil = new Date(cachedResult.metadata.freshUntil); if (freshUntil >= new Date()) { return { isCached: true, isStale: false, body: cachedResult.data, headers: cachedResultMetadata.headers, statusCode: cachedResultMetadata.statusCode, }; } } let freshResult: Response; try { freshResult = await got(resourceUrl, { headers: {'user-agent': userAgent || `skypack/v0.0.1`}, throwHttpErrors: false, responseType: 'buffer', }); } catch (err) { if (cachedResult) { const cachedResultMetadata = cachedResult.metadata as ResourceCacheMetadata; return { isCached: true, isStale: true, body: cachedResult.data, headers: cachedResultMetadata.headers, statusCode: cachedResultMetadata.statusCode, }; } throw err; } const cacheUntilMatch = freshResult.headers['cache-control']?.match(/max-age=(\d+)/); if (cacheUntilMatch) { var freshUntil = new Date(); freshUntil.setSeconds(freshUntil.getSeconds() + parseInt(cacheUntilMatch[1])); // no need to await, since we `.catch()` to swallow any errors. cacache .put(RESOURCE_CACHE, resourceUrl, freshResult.body, { metadata: { headers: freshResult.headers, statusCode: freshResult.statusCode, freshUntil: freshUntil.toUTCString(), } as ResourceCacheMetadata, }) .catch(() => null); } return { body: freshResult.body, headers: freshResult.headers, statusCode: freshResult.statusCode, isCached: false, isStale: false, }; } async buildNewPackage( spec: string, semverString?: string, userAgent?: string, ): Promise { const [packageName, packagePath] = parseRawPackageImport(spec); const lookupUrl = `/new/${packageName}` + (semverString ? `@${semverString}` : ``) + (packagePath ? `/${packagePath}` : ``); try { const {statusCode} = await this.fetch(lookupUrl, userAgent); return { error: null, success: statusCode !== 500, }; } catch (err) { return {error: err, success: false}; } } async lookupBySpecifier( spec: string, semverString?: string, qs?: string, userAgent?: string, ): Promise { const [packageName, packagePath] = parseRawPackageImport(spec); const lookupUrl = `/${packageName}` + (semverString ? `@${semverString}` : ``) + (packagePath ? `/${packagePath}` : ``) + (qs ? `?${qs}` : ``); try { const {body, statusCode, headers, isCached, isStale} = await this.fetch(lookupUrl, userAgent); if (statusCode !== 200) { return {error: new Error(body.toString())}; } return { error: null, body, isCached, isStale, importStatus: headers['x-import-status'] as string, importUrl: headers['x-import-url'] as string, pinnedUrl: headers['x-pinned-url'] as string | undefined, typesUrl: headers['x-typescript-types'] as string | undefined, }; } catch (err) { return {error: err}; } } async installTypes(spec: string, semverString?: string, dir?: string) { dir = dir || path.join(process.cwd(), '.types'); const lookupResult = await this.lookupBySpecifier(spec, semverString, 'dts'); if (lookupResult.error) { throw lookupResult.error; } if (!lookupResult.typesUrl) { throw new Error(`Skypack CDN: No types found "${spec}"`); } const typesTarballUrl = lookupResult.typesUrl.replace(/(mode=types.*?)\/.*/, '$1/all.tgz'); await mkdirp(dir); const tempDir = await cacache.tmp.mkdir(RESOURCE_CACHE); let tarballContents: Buffer; const cachedTarball = await cacache .get(RESOURCE_CACHE, typesTarballUrl) .catch((/* ignore */) => null); if (cachedTarball) { tarballContents = cachedTarball.data; } else { const tarballResponse = await this.fetch(typesTarballUrl); if (tarballResponse.statusCode !== 200) { throw new Error(tarballResponse.body.toString()); } tarballContents = tarballResponse.body as any as Buffer; await cacache.put(RESOURCE_CACHE, typesTarballUrl, tarballContents); } const typesUrlParts = url.parse(typesTarballUrl).pathname!.split('/'); const typesPackageName = url.parse(typesTarballUrl).pathname!.startsWith('/-/@') ? typesUrlParts[2] + '/' + typesUrlParts[3].split('@')[0] : typesUrlParts[2].split('@')[0]; const typesPackageTarLoc = path.join(tempDir, `${typesPackageName}.tgz`); if (typesPackageName.includes('/')) { await mkdirp(path.dirname(typesPackageTarLoc)); } fs.writeFileSync(typesPackageTarLoc, tarballContents); const typesPackageLoc = path.join(dir, typesPackageName); await mkdirp(typesPackageLoc); await tar.x({ file: typesPackageTarLoc, cwd: typesPackageLoc, }); } } interface ResourceCacheMetadata { headers: IncomingHttpHeaders; statusCode: number; freshUntil: string; } export type BuildNewPackageResponse = | {error: Error; success: false} | { error: null; success: boolean; }; export type LookupBySpecifierResponse = | {error: Error} | { error: null; body: Buffer; isCached: boolean; isStale: boolean; importStatus: string; importUrl: string; pinnedUrl: string | undefined; typesUrl: string | undefined; }; export async function clearCache() { return cacache.rm.all(RESOURCE_CACHE); } ================================================ FILE: skypack/src/rollup-plugin-remote-cdn.ts ================================================ import cacache from 'cacache'; import {Plugin, ResolvedId} from 'rollup'; import {SkypackSDK} from './index'; import {AbstractLogger, HAS_CDN_HASH_REGEX, RESOURCE_CACHE} from './util'; /** * rollup-plugin-remote-cdn * * Load import URLs from a remote CDN, sitting behind a local cache. The local * cache acts as a go-between for the resolve & load step: when we get back a * successful CDN resolution, we save the file to the local cache and then tell * rollup that it's safe to load from the cache in the `load()` hook. */ export function rollupPluginSkypack({sdk, logger}: {sdk: SkypackSDK; logger: AbstractLogger}) { const CACHED_FILE_ID_PREFIX = 'remote-pkg-cache:'; return { name: 'snowpack:rollup-plugin-remote-cdn', async resolveId(source: string, importer) { let cacheKey: string; if (source.startsWith(sdk.origin)) { cacheKey = source; } else if (source.startsWith('/-/')) { cacheKey = sdk.origin + source; } else if (source.startsWith('/pin/')) { cacheKey = sdk.origin + source; } else { return null; } // If the source path is a CDN path including a hash, it's assumed the // file will never change and it is safe to pull from our local cache // without a network request. logger.debug(`resolve ${cacheKey}`, {name: 'install:remote'}); if (HAS_CDN_HASH_REGEX.test(cacheKey)) { const cachedResult = await cacache.get .info(RESOURCE_CACHE, cacheKey) .catch((/* ignore */) => null); if (cachedResult) { return CACHED_FILE_ID_PREFIX + cacheKey; } } // Otherwise, make the remote request and cache the file on success. const {statusCode} = await sdk.fetch(cacheKey); if (statusCode === 200) { return CACHED_FILE_ID_PREFIX + cacheKey; } // If lookup failed, skip this plugin and resolve the import locally instead. // TODO: Log that this has happened (if some sort of verbose mode is enabled). const packageName = cacheKey .substring(sdk.origin.length) .replace('/-/', '') .replace('/pin/', '') .split('@')[0]; return this.resolve(packageName, importer!, {skipSelf: true}).then((resolved) => { let finalResult = resolved; if (!finalResult) { finalResult = {id: packageName} as any as ResolvedId; } return finalResult; }); }, async load(id: string) { if (!id.startsWith(CACHED_FILE_ID_PREFIX)) { return null; } const cacheKey = id.substring(CACHED_FILE_ID_PREFIX.length); logger.debug(`load ${cacheKey}`, {name: 'install:remote'}); const {body} = await sdk.fetch(cacheKey); return body.toString(); }, } as Plugin; } ================================================ FILE: skypack/src/util.ts ================================================ import globalCacheDir from 'cachedir'; import etag from 'etag'; import findUp from 'find-up'; import fs from 'fs'; import mkdirp from 'mkdirp'; import path from 'path'; export interface AbstractLogger { debug: (...args: any[]) => void; log: (...args: any[]) => void; warn: (...args: any[]) => void; error: (...args: any[]) => void; } export interface ImportMap { imports: {[packageName: string]: string}; } export const GLOBAL_CACHE_DIR = globalCacheDir('skypack'); export const RESOURCE_CACHE = path.join(GLOBAL_CACHE_DIR, 'pkg-cache-3.0'); export const HAS_CDN_HASH_REGEX = /\-[a-zA-Z0-9]{16,}/; // A note on cache naming/versioning: We currently version our global caches // with the version of the last breaking change. This allows us to re-use the // same cache across versions until something in the data structure changes. // At that point, bump the version in the cache name to create a new unique // cache name. export const BUILD_CACHE = path.join(GLOBAL_CACHE_DIR, 'build-cache-2.7'); const LOCKFILE_HASH_FILE = '.hash'; // NOTE(fks): Must match empty script elements to work properly. export const HTML_JS_REGEX = /()(.*?)<\/script>/gms; export const CSS_REGEX = /@import\s*['"](.*)['"];/gs; export const SVELTE_VUE_REGEX = /(]*>)(.*?)<\/script>/gms; export const URL_HAS_PROTOCOL_REGEX = /^(\w+:)?\/\//; const UTF8_FORMATS = ['.css', '.html', '.js', '.map', '.mjs', '.json', '.svg', '.txt', '.xml']; export function getEncodingType(ext: string): 'utf-8' | undefined { return UTF8_FORMATS.includes(ext) ? 'utf-8' : undefined; } export async function readLockfile(cwd: string): Promise { try { var lockfileContents = fs.readFileSync(path.join(cwd, 'snowpack.lock.json'), { encoding: 'utf-8', }); } catch (err) { // no lockfile found, ignore and continue return null; } // If this fails, we actually do want to alert the user by throwing return JSON.parse(lockfileContents); } export async function writeLockfile(loc: string, importMap: ImportMap): Promise { const sortedImportMap: ImportMap = {imports: {}}; for (const key of Object.keys(importMap.imports).sort()) { sortedImportMap.imports[key] = importMap.imports[key]; } fs.writeFileSync(loc, JSON.stringify(sortedImportMap, undefined, 2), {encoding: 'utf-8'}); } export function isTruthy(item: T | false | null | undefined): item is T { return Boolean(item); } /** Get the package name + an entrypoint within that package (if given). */ export function parsePackageImportSpecifier(imp: string): [string, string | null] { const impParts = imp.split('/'); if (imp.startsWith('@')) { const [scope, name, ...rest] = impParts; return [`${scope}/${name}`, rest.join('/') || null]; } const [name, ...rest] = impParts; return [name, rest.join('/') || null]; } /** * Given a package name, look for that package's package.json manifest. * Return both the manifest location (if believed to exist) and the * manifest itself (if found). * * NOTE: You used to be able to require() a package.json file directly, * but now with export map support in Node v13 that's no longer possible. */ export function resolveDependencyManifest(dep: string, cwd: string): [string | null, any | null] { // Attempt #1: Resolve the dependency manifest normally. This works for most // packages, but fails when the package defines an export map that doesn't // include a package.json. If we detect that to be the reason for failure, // move on to our custom implementation. try { const depManifest = require.resolve(`${dep}/package.json`, {paths: [cwd]}); return [depManifest, require(depManifest)]; } catch (err) { // if its an export map issue, move on to our manual resolver. if (err.code !== 'ERR_PACKAGE_PATH_NOT_EXPORTED') { return [null, null]; } } // Attempt #2: Resolve the dependency manifest manually. This involves resolving // the dep itself to find the entrypoint file, and then haphazardly replacing the // file path within the package with a "./package.json" instead. It's not as // thorough as Attempt #1, but it should work well until export maps become more // established & move out of experimental mode. let result = [null, null] as [string | null, any | null]; try { const fullPath = require.resolve(dep, {paths: [cwd]}); // Strip everything after the package name to get the package root path // NOTE: This find-replace is very gross, replace with something like upath. const searchPath = `${path.sep}node_modules${path.sep}${dep.replace('/', path.sep)}`; const indexOfSearch = fullPath.lastIndexOf(searchPath); if (indexOfSearch >= 0) { const manifestPath = fullPath.substring(0, indexOfSearch + searchPath.length + 1) + 'package.json'; result[0] = manifestPath; const manifestStr = fs.readFileSync(manifestPath, {encoding: 'utf-8'}); result[1] = JSON.parse(manifestStr); } } catch (err) { // ignore } finally { return result; } } /** * If Rollup erred parsing a particular file, show suggestions based on its * file extension (note: lowercase is fine). */ export const MISSING_PLUGIN_SUGGESTIONS: {[ext: string]: string} = { '.svelte': 'Try installing rollup-plugin-svelte and adding it to Snowpack (https://www.snowpack.dev/tutorials/svelte)', '.vue': 'Try installing rollup-plugin-vue and adding it to Snowpack (https://www.snowpack.dev/guides/vue)', }; export async function checkLockfileHash(dir: string) { const lockfileLoc = await findUp(['package-lock.json', 'yarn.lock']); if (!lockfileLoc) { return true; } const hashLoc = path.join(dir, LOCKFILE_HASH_FILE); const newLockHash = etag(await fs.promises.readFile(lockfileLoc, 'utf-8')); const oldLockHash = await fs.promises.readFile(hashLoc, 'utf-8').catch(() => ''); return newLockHash === oldLockHash; } export async function updateLockfileHash(dir: string) { const lockfileLoc = await findUp(['package-lock.json', 'yarn.lock']); if (!lockfileLoc) { return; } const hashLoc = path.join(dir, LOCKFILE_HASH_FILE); const newLockHash = etag(await fs.promises.readFile(lockfileLoc)); await mkdirp(path.dirname(hashLoc)); await fs.promises.writeFile(hashLoc, newLockHash); } /** * For the given import specifier, return an alias entry if one is matched. */ export function findMatchingAliasEntry( alias: Record, spec: string, ): {from: string; to: string; type: 'package' | 'path'} | undefined { // Only match bare module specifiers. relative and absolute imports should not match if ( spec === '.' || spec === '..' || spec.startsWith('./') || spec.startsWith('../') || spec.startsWith('/') || spec.startsWith('http://') || spec.startsWith('https://') ) { return undefined; } for (const [from, to] of Object.entries(alias)) { let foundType: 'package' | 'path' = isPackageAliasEntry(to) ? 'package' : 'path'; const isExactMatch = spec === removeTrailingSlash(from); const isDeepMatch = spec.startsWith(addTrailingSlash(from)); if (isExactMatch || isDeepMatch) { return { from, to, type: foundType, }; } } } /** * For the given import specifier, return an alias entry if one is matched. */ export function isPackageAliasEntry(val: string): boolean { return !path.isAbsolute(val); } /** Get full extensions of files */ export function getExt(fileName: string) { return { /** base extension (e.g. `.js`) */ baseExt: path.extname(fileName).toLocaleLowerCase(), /** full extension, if applicable (e.g. `.proxy.js`) */ expandedExt: path.basename(fileName).replace(/[^.]+/, '').toLocaleLowerCase(), }; } /** Replace file extensions */ export function replaceExt(fileName: string, oldExt: string, newExt: string): string { const extToReplace = new RegExp(`\\${oldExt}$`, 'i'); return fileName.replace(extToReplace, newExt); } /** * Sanitizes npm packages that end in .js (e.g `tippy.js` -> `tippyjs`). * This is necessary because Snowpack can’t create both a file and directory * that end in .js. */ export function sanitizePackageName(filepath: string): string { const dirs = filepath.split('/'); const file = dirs.pop() as string; return [...dirs.map((path) => path.replace(/\.js$/i, 'js')), file].join('/'); } // Source Map spec v3: https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit#heading=h.lmz475t4mvbx /** CSS sourceMappingURL */ export function cssSourceMappingURL(code: string, sourceMappingURL: string) { return code + `/*# sourceMappingURL=${sourceMappingURL} */`; } /** JS sourceMappingURL */ export function jsSourceMappingURL(code: string, sourceMappingURL: string) { return code.replace(/\n*$/, '') + `\n//# sourceMappingURL=${sourceMappingURL}\n`; // strip ending lines & append source map (with linebreaks for safety) } export function removeLeadingSlash(path: string) { return path.replace(/^[/\\]+/, ''); } export function removeTrailingSlash(path: string) { return path.replace(/[/\\]+$/, ''); } export function addLeadingSlash(path: string) { return path.replace(/^\/?/, '/'); } export function addTrailingSlash(path: string) { return path.replace(/\/?$/, '/'); } ================================================ FILE: skypack/tsconfig.json ================================================ { "include": ["src"], "compilerOptions": { "outDir": "lib", "module": "commonjs", "target": "es2018", "moduleResolution": "node", "declaration": true, "esModuleInterop": true, "strict": true, "sourceMap": true, "noImplicitAny": false, "noUnusedLocals": true, "noUnusedParameters": true, "skipLibCheck": true } } ================================================ FILE: snowpack/.gitignore ================================================ lib node_modules vendor/generated/ vendor/types/ ================================================ FILE: snowpack/.npmignore ================================================ src ================================================ FILE: snowpack/CHANGELOG.md ================================================ # snowpack ## 3.8.8 ### Patch Changes - 57ee22fc: Fix for dynamic import scanning ## 3.8.7 ### Patch Changes - d72dc7df: Bugfix: dev server hanging on circular dependencies - c6146e61: Fixes a ghost dependency on 'magic-string' ## 3.8.6 ### Patch Changes - 0e9829ce: Fixes a regression caused by #3597 ## 3.8.5 ### Patch Changes - c103f4d4: Downgrade skypack versions ## 3.8.4 ### Patch Changes - 47d0792f: Fix using assets from linked packages - 57f545cc: Fix issue where multiple onFileChange handlers overwrote each other - Updated dependencies [5e84cdd8] - skypack@0.3.3 ## 3.8.3 ### Patch Changes - 91da2b6a: Fix a small SSR bug with default exports ## 3.8.2 ### Patch Changes - c4a55550: Fixes scanning of dynamic imports on packages ## 3.8.1 ### Patch Changes - a1f56ef7: Remove type attribute from HMR script in dev server - 95d4309a: Remove exports field from package.json - Updated dependencies [9b1472f6] - esinstall@1.1.7 ## 3.8.0 ### Minor Changes - a3eadde7: Ship unbundled ESM & CJS Snowpack builds ### Patch Changes - f4e0bc42: Fixes regression with modules executing more than once - ab4ddec3: Fix dot folder dev response ## 3.7.1 ### Patch Changes - a4768503: Fix CSS Modules + baseUrl (#3515) ## 3.7.0 ### Minor Changes - 4ab4f07e: configure if dotfiles are included in build (#3455) ### Patch Changes - 101dc42f: Pass `external` modules through to `esbuild`. (#3499) - fe288ca4: Respect trailing slash in dev server (#3509) - 7939563b: Don’t scan imports from non-JS files (#3495) - e449b7b8: [ci] yarn format - 6506cff2: Chore: update deps (#3494) - 30b7ef45: Ensure remote package fetches always use the configured origin (#3397) ## 3.6.2 ### Patch Changes - 33cd5648: Pass real filepath to transform plugins (#3483) ## 3.6.1 ### Patch Changes - 110043e2: Check externals before resolving entrypoint (#3479) ## 3.6.0 ### Minor Changes - ffee2c57: allow configuring loader (#3377) ### Patch Changes - fb7eaaa4: Allow `external` CommonJS modules to be imported properly in Node (#3473) - 8cff0336: Make import.meta.url be the file URL in SSR (#3472) - 29e609aa: [#3188] Add ant-design/icons to NEVER_PEER_PACKAGES (#3443) - ded85e8a: Minor optimization on resolving path starts with '.' (#3361) - b8c76b00: Fix websocket proxying (#3225) ## 3.5.9 ### Patch Changes - 343274e2: [ci] yarn format - d93d01ec: Fix race condition reading existing import-map.json files (#3453) ## 3.5.8 ### Patch Changes - 611dac9a: [ci] yarn format - 7a1ae463: Handle runtime invalidation for proxy files (#3449) ## 3.5.7 ### Patch Changes - 6e7f137b: [ci] yarn format - dc44eb75: Disconnect the HMR server when snowpack is shutdown (#3439) ## 3.5.6 ### Patch Changes - f0534bb7: [ci] yarn format - 6ef3695d: Allow Snowpack to scan .astro files for imports (#3423) ## 3.5.5 ### Patch Changes - d61fb0a8: [ci] yarn format - aed6a8fd: Modules marked as `external` should always be treated as external (#3408) - cd6ec781: [ci] yarn format - 64b377e3: Correctly handle subpackage imports marked as external (#3407) ## 3.5.4 ### Patch Changes - 78d6ecf1: Fix: remove external override (#3401) ## 3.5.3 ### Patch Changes - c659b7c3: [ci] yarn format - b375b8a3: Fix `external` behavior for local package source and SSR. (#3399) ## 3.5.2 ### Patch Changes - 0f65cdf2: fix: construct NotFoundError before throwing (#3380) ## 3.5.1 ### Patch Changes - 398ad9f1: Allow clean shutdown of dev server (#3349) ## 3.5.0 ### Minor Changes - 84e39f8a: Enable cache directory path to be set explicitly (#3064) ### Patch Changes - 91bb53c0: Don’t cache .css files for Tailwind projects (#3326) - 48f9c524: [ci] yarn format - 45e2a22b: Avoid uncaught exception when file is deleted (#3313) - e321bbee: resolve `.css` ESM imports to `.css.{js,ts}` if there's no `.css` file on disk (#3315) ## 3.4.0 ### Minor Changes - 4403595e: Sourcemaps (#3271) ### Patch Changes - fa7a5e3f: Typo (#3291) - 680272eb: Fix appending port 80 to url sometimes breaks hmr connectivity #3268 (#3269) - dc60a025: chore: update create-snowpack-app template deps (#3233) ## 3.3.7 ### Patch Changes - 5e34b829: Fix missing build files from load plugins (#3230) ## 3.3.6 ### Patch Changes - 9ad97afe: fix: CSS Modules exporting {} when no mount config (#3229) - ff1eaf66: [ci] yarn format - 0707fd63: fix: add mounted node_modules to ignore list (#3227) - f53e1833: chore: bump Snowpack version (#3228) - 3701201b: Support packageOptions.rollup.plugins (#3123) - cea40a05: Removes console log (#3204) - 3af6e064: [ci] yarn format - 1902f5c2: Correctly resolve preload urls from build manifest (#3201) ## 3.3.4 ### Patch Changes - 8f044398: Restore proxy SSR rewriting for CSS Modules proxy (#3191) - 870b34c2: Take the result of the first plugin (#3190) - 758bdb27: Fix Sass + CSS Modules (#3186) - 5e58fc64: fix: Race condition in symlink directory read (#3181) - 28ea1f86: [ci] yarn format - e6861227: Support mounting within node_modules directory (#3134) - a089d86a: Test fixtures continued (#3100) ## 3.3.3 ### Patch Changes - 1f82543d: Fix publicPath option in esbuild (#3175) - 83294444: Add CSS Modules in Build SSR (#3170) ## 3.3.2 ### Patch Changes - dfd200a8: [ci] yarn format - fa96e618: Use a separate PackageSource for each config (#3164) ## 3.3.1 ### Patch Changes - 35e38b6c: [ci] yarn format - a3951e31: Refactor the build to improve reusability (#3157) ## 3.3.0 ### Minor Changes - deacbcd0: Streaming Package Imports v2 (#3028) - d9956f73: add explicit "mode" config (#3135) ### Patch Changes - 25ed2885: [ci] yarn format - 6a15f872: Fix env.js always having MODE and NODE_ENV as development (#3132) - 63e30d32: Revert "improve node_modules excluding" - 3b3402bb: improve node_modules excluding - 48618f05: fix: remove tailing slash in import specifier as esinstall do (#3102) - b0481aa8: Honor devOptions.hmr option, fixes #3105 (#3114) - 42da07e4: fix: sourcemap config copy paste typo in config.ts in pr #2793 (#3115) - 9eb50368: update deps (#2928) - 18b9f09b: Simplify test suite (#2858) ## 3.2.2 ### Patch Changes - a643af33: Detect Sass partial changes in dev (#3060) ## 3.2.1 ### Patch Changes - 0663c1ac: fix HMR issue ## 3.2.0 ### Minor Changes - 598f05a7: Feat: import.meta.glob and import.meta.globEager support (#2881) ### Patch Changes - 56c5a231: fix typo in changelog - c3971f10: add snowpack release notes - 40f02cf4: reorder changelog entries - a25f407d: Update CHANGELOG.md - c9dacc29: Update CHANGELOG.md - a676d665: fix website package.json - 68df7237: support remote import if specifier has version in it - 16f58b99: [ci] yarn format - d3859c8d: fix lint - 57a65bb8: [ci] yarn format - 685f2200: add docs, cleanup handler - 5fe83d4d: [ci] yarn format - 844cbcbe: routes support upgrade event (#2988) - a17dd97c: Fix: Only import directory/index.js as a fallback mechanism (#3018) - e340dea9: Do not exclude folders starting with a dot (#2962) - fc6c1417: Improve CSS error message (#2973) - b41b14b0: Await promises in local source (#2972) ## 3.1.2 ### Patch Changes - e50cb745: fix better handling for unscannable cjs packages - 1232e252: add back cjs-cjs compat from an earlier pr (#2934) ## 3.1.1 ### Patch Changes - f53498ba: Fix preact regression (#2926) ## 3.1.0 ### Minor Changes - bea1c56c: Simplify. cleanup, enhance snowpack internals (#2707) - 3ba7fede: feat: support specifying sourcemap type in config.optimize.sourcemap (#2793) - aa953cca: refactor: update to esbuild 0.9 (#2886) ### Patch Changes - 872bfc74: [ci] yarn format - c8aee80f: Support compound output file extensions (#2593) - 8dbd6af3: [ci] yarn format - 88d3a9a9: handle legacy package urls (#2915) - 094b7a12: Add `openUrl` option to config (#2902) - aba028c4: [ci] yarn format - 4de244ef: Add support for custom TLS key files via devOptions.secure = { cert, key } (#2356) - da049ce4: [ci] yarn format - b569d35b: add getUrlForPackage utility (#2913) - d826a60c: Remove enumerability of converted ESM in SSR (#2920) - 941110cd: fix esbuild 0.9.x breaking change (#2914) - 35289b4b: improve import scanner perf (#2900) - a800bf3d: finalize picomatch support (#2912) - d33076a3: Speed up exclusions with picomatch (#2904) - 84e034cc: [ci] yarn format - f35bc421: fix debug log - 81fd028e: [ci] yarn format - 7c8236e8: Add config.env property to support non-prefixed env variables (#2390) - 337cfd7a: only target actual imports (Regex Fix) (#2817) - cd4f344b: [ci] yarn format - 0fce7efd: Support buildOptions.jsxInject in config (#2884) - dc503c84: speed up mount scanning with fdir (#2876) - e8dead62: improve handling of "." & ".." imports (#2877) - a178f286: run transform on all dependencies (#2878) - 27321b83: collect known imports by package and version - 3a6ad53a: update (#2363) - b2738967: improve cjs<>esm conversion of named exports (#2859) - 03ceb62d: Resolve missing extensions (#2839) - 28689888: [ci] yarn format - 689f58e9: SSR fixes (#2853) - 3549e43c: Fix Mac OS devOptions.open bug (#2798) - 21461ce5: Update @snowpack/plugin-postcss API (#2854) - 9fbe93fb: fix: revert snowpack to v3.0.13 for CI tests (#2851) - 339d3cf1: [ci] yarn format - ea4ebd17: Proper loading of ESM config files (#2834) - 8f022949: 3.1.0 - 9d987e4a: fix watching logic and dashboard output - 15428ae4: [ci] yarn format - 43677e89: Add import support for .interface files, alongside .svelte, and .vue. (#2380) - 3d457a8e: fix(ssr-loader): support css modules (#2528) - 23d7b450: fix: recognise dot as a valid relative import (#2662) - 582808c3: [ci] yarn format - deabcb0e: don't escape utf-8 characters by cheerio and esbuild (#2755) - d35d50b4: [ci] yarn format - b756b49d: Fix snowpack add/rm for npm packages with @ prefix (#2665) - 58df86d4: add a tip if someone uses process.env - fdda447d: dont match meta paths to routes ## 3.0.13 ### Patch Changes - 74d0be10: add resolve for bundled package ## 3.0.12 ### Patch Changes - d3b6f769: Support packages that use export maps but have no main (#2659) - 2ae933be: add true logger error message - 0de849ee: Catch WebSocket message parse errors (#2470) - da75ff23: remove circular dependencies in util (#2366) - bfa32aff: proper default browser support for mac (#2364) - 1c44c22d: fix bad url access in import maps, snowpack add - 76dca31a: add octet-stream handling - 10f3461b: [ci] yarn format - 615f13a7: Add isHmrEnabled and isSSR to plugin transform. (#2332) - 65008c63: fix: prettier snowpack/src/build dir (#2346) - 690d3b1d: update prettier to use explicit formatting - f07105a9: Add root level export of clearCache (#2330) - 137139b4: [ci] yarn format - e0f01181: fix: prettier format (#2316) - 0fc858cf: fix bad optimize external filter - fb167098: load plugins relative to config root - 85ddf618: add JS API docs (#2315) - 49b29f97: [ci] yarn format ## 3.0.11 ### Patch Changes - 58a36d4c: fix bad build conditional check - 37f7a55d: remove empty build folders from build - 6e01eeb5: improve build watch output - 137c58b0: fix init file issue - e2b74e7b: add fallback deprecation - 968720cf: remove rollup from bundle to pass peerDep checks - 70d25dad: Resolves #2265 (#2289) - 7b38b486: get tests passing - e6b618c5: [ci] yarn format ## 3.0.10 ### Patch Changes - 1d071628: cleanup the bundled build before publishing - beb3f9a1: stop ignoring postcss in generated bundle - 2b797521: [ci] yarn format ## 3.0.9 ### Patch Changes - d26c7733: [ci] yarn format ## 3.0.8 ### Patch Changes - c2124bd7: json fix - e040f071: Fix postcss-modules being required as a dependency in projects (#2257) - c566aa10: add bundled type support for esinstall and skypack, remove as full deps - ecf85d52: move open back to regular dependency - b0f7280c: [ci] yarn format ## 3.0.7 ### Patch Changes - 61bd4c3b: [ci] yarn format - eb5b366a: cleanup types, release script - 20e3ed21: move skypack and esbuild into real dependencies for now - e63d70fd: fix direct foo.svelte.css references - 01a80448: [ci] yarn format ## 3.0.6 ### Patch Changes - 8f614490: final cleanup before v3 goes out - 15d77ea3: cleanup ================================================ FILE: snowpack/LICENSE ================================================ MIT License Copyright (c) 2019 Fred K. Schott Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ This license applies to parts of the src/dev.ts file originating from the https://github.com/lukejacksonn/servor repository: MIT License Copyright (c) 2019 Luke Jackson Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: snowpack/README.md ================================================ [![CI](https://github.com/withastro/snowpack/workflows/CI/badge.svg?event=push)](https://github.com/withastro/snowpack/actions) ### What is Snowpack? **Snowpack is a modern, lightweight toolchain for web application development.** Traditional dev bundlers like webpack or Parcel need to rebuild & rebundle entire chunks of your application every time you save a single file. This introduces lag between changing a file and seeing those changes reflected in the browser, sometimes as slow as several seconds. Snowpack solves this problem by serving your application **unbundled in development.** Any time you change a file, Snowpack never rebuilds more than a single file. There's no bundling to speak of, just a few milliseconds of single-file rebuilding and then an instant update in the browser via HMR. We call this new approach **O(1) Build Tooling.** You can read more about it in our [Snowpack 2.0 Release Post.](https://www.snowpack.dev/posts/2020-05-26-snowpack-2-0-release/) When you're ready to deploy your web application to users, you can add back a traditional bundler like Webpack or Parcel. With Snowpack you get bundled & optimized production performance without sacrificing dev speed by adding an unnecessary bundler, ### Key Features - A dev environment that starts up in **50ms or less.** - Changes are reflected [instantly in the browser.](https://www.snowpack.dev/posts/2020-05-26-snowpack-2-0-release/#hot-module-replacement) - Integrates your favorite bundler for [production-optimized builds.](https://www.snowpack.dev/posts/2020-05-26-snowpack-2-0-release/#snowpack-build) - Out-of-the-box support for [TypeScript, JSX, CSS Modules and more.](https://www.snowpack.dev/posts/2020-05-26-snowpack-2-0-release/#features) - Connect your favorite tools with custom [build scripts](https://www.snowpack.dev/posts/2020-05-26-snowpack-2-0-release/#build-scripts) & [third-party plugins.](https://www.snowpack.dev/posts/2020-05-26-snowpack-2-0-release/#build-plugins) **💁 More info at the official [Snowpack website ➞](https://snowpack.dev)** ================================================ FILE: snowpack/assets/hmr-client.js ================================================ /** * esm-hmr/runtime.ts * A client-side implementation of the ESM-HMR spec, for reference. */ const isWindowDefined = typeof window !== 'undefined'; function log(...args) { console.log('[ESM-HMR]', ...args); } function reload() { if (!isWindowDefined) { return; } location.reload(true); } /** Clear all error overlays from the page */ function clearErrorOverlay() { if (!isWindowDefined) { return; } document.querySelectorAll('hmr-error-overlay').forEach((el) => el.remove()); } /** Create an error overlay (if custom element exists on the page). */ function createNewErrorOverlay(data) { if (!isWindowDefined) { return; } const HmrErrorOverlay = customElements.get('hmr-error-overlay'); if (HmrErrorOverlay) { const overlay = new HmrErrorOverlay(data); clearErrorOverlay(); document.body.appendChild(overlay); } } let SOCKET_MESSAGE_QUEUE = []; function _sendSocketMessage(msg) { socket.send(JSON.stringify(msg)); } function sendSocketMessage(msg) { if (socket.readyState !== socket.OPEN) { SOCKET_MESSAGE_QUEUE.push(msg); } else { _sendSocketMessage(msg); } } let socketURL = isWindowDefined && window.HMR_WEBSOCKET_URL; if (!socketURL) { const socketHost = isWindowDefined && window.HMR_WEBSOCKET_PORT && window.HMR_WEBSOCKET_PORT !== 80 ? `${location.hostname}:${window.HMR_WEBSOCKET_PORT}` : location.host; socketURL = (location.protocol === 'http:' ? 'ws://' : 'wss://') + socketHost + '/'; } const socket = new WebSocket(socketURL, 'esm-hmr'); socket.addEventListener('open', () => { SOCKET_MESSAGE_QUEUE.forEach(_sendSocketMessage); SOCKET_MESSAGE_QUEUE = []; }); const REGISTERED_MODULES = {}; class HotModuleState { constructor(id) { this.data = {}; this.isLocked = false; this.isDeclined = false; this.isAccepted = false; this.acceptCallbacks = []; this.disposeCallbacks = []; this.id = id; } lock() { this.isLocked = true; } dispose(callback) { this.disposeCallbacks.push(callback); } invalidate() { reload(); } decline() { this.isDeclined = true; } accept(_deps, callback = true) { if (this.isLocked) { return; } if (!this.isAccepted) { sendSocketMessage({id: this.id, type: 'hotAccept'}); this.isAccepted = true; } if (!Array.isArray(_deps)) { callback = _deps || callback; _deps = []; } if (callback === true) { callback = () => {}; } const deps = _deps.map((dep) => { const ext = dep.split('.').pop(); if (!ext) { dep += '.js'; } else if (ext !== 'js') { dep += '.proxy.js'; } return new URL(dep, `${window.location.origin}${this.id}`).pathname; }); this.acceptCallbacks.push({ deps, callback, }); } } export function createHotContext(fullUrl) { const id = new URL(fullUrl).pathname; const existing = REGISTERED_MODULES[id]; if (existing) { existing.lock(); runModuleDispose(id); return existing; } const state = new HotModuleState(id); REGISTERED_MODULES[id] = state; return state; } /** Called when any CSS file is loaded. */ async function runCssStyleAccept({url: id}) { const nonce = Date.now(); const oldLinkEl = document.head.querySelector(`link[data-hmr="${id}"]`) || document.head.querySelector(`link[href="${id}"]`); if (!oldLinkEl) { return true; } const linkEl = oldLinkEl.cloneNode(false); linkEl.dataset.hmr = id; linkEl.type = 'text/css'; linkEl.rel = 'stylesheet'; linkEl.href = id + '?mtime=' + nonce; linkEl.addEventListener( 'load', // Once loaded, remove the old link element (with some delay, to avoid FOUC) () => setTimeout(() => document.head.removeChild(oldLinkEl), 30), false, ); oldLinkEl.parentNode.insertBefore(linkEl, oldLinkEl); return true; } /** Called when a new module is loaded, to pass the updated module to the "active" module */ async function runJsModuleAccept({url: id, bubbled}) { const state = REGISTERED_MODULES[id]; if (!state) { return false; } if (state.isDeclined) { return false; } const acceptCallbacks = state.acceptCallbacks; const updateID = Date.now(); for (const {deps, callback: acceptCallback} of acceptCallbacks) { const [module, ...depModules] = await Promise.all([ import(id + `?mtime=${updateID}`), ...deps.map((d) => import(d + `?mtime=${updateID}`)), ]); acceptCallback({module, bubbled, deps: depModules}); } return true; } /** Called when a new module is loaded, to run cleanup on the old module (if needed) */ async function runModuleDispose(id) { const state = REGISTERED_MODULES[id]; if (!state) { return false; } if (state.isDeclined) { return false; } const disposeCallbacks = state.disposeCallbacks; state.disposeCallbacks = []; state.data = {}; disposeCallbacks.map((callback) => callback()); return true; } socket.addEventListener('message', ({data: _data}) => { if (!_data) { return; } const data = JSON.parse(_data); if (data.type === 'reload') { log('message: reload'); reload(); return; } if (data.type === 'error') { console.error( `[ESM-HMR] ${data.fileLoc ? data.fileLoc + '\n' : ''}`, data.title + '\n' + data.errorMessage, ); createNewErrorOverlay(data); return; } if (data.type === 'update') { log('message: update', data); (data.url.endsWith('.css') ? runCssStyleAccept(data) : runJsModuleAccept(data)) .then((ok) => { if (ok) { clearErrorOverlay(); } else { reload(); } }) .catch((err) => { console.error('[ESM-HMR] Hot Update Error', err); // A failed import gives a TypeError, but invalid ESM imports/exports give a SyntaxError. // Failed build results already get reported via a better WebSocket update. // We only want to report invalid code like a bad import that doesn't exist. if (err instanceof SyntaxError) { createNewErrorOverlay({ title: 'Hot Update Error', fileLoc: data.url, errorMessage: err.message, errorStackTrace: err.stack, }); } }); return; } log('message: unknown', data); }); log('listening for file changes...'); /** Runtime error reporting: If a runtime error occurs, show it in an overlay. */ isWindowDefined && window.addEventListener('error', function (event) { if (window.snowpackHmrErrorOverlayIgnoreErrors) { const ignoreErrors = window.snowpackHmrErrorOverlayIgnoreErrors; for (const item of ignoreErrors) { if (event.message && event.message.match(item)) { console.warn('[ESM-HMR] Hmr Error Overlay Ignored', event.message); return; } } } // Generate an "error location" string let fileLoc; if (event.filename) { fileLoc = event.filename; if (event.lineno !== undefined) { fileLoc += ` [:${event.lineno}`; if (event.colno !== undefined) { fileLoc += `:${event.colno}`; } fileLoc += `]`; } } let errorMessage = event.message; if (event.message === 'Uncaught ReferenceError: process is not defined') { errorMessage += `\n(Tip: Node's "process" global does not exist in Snowpack. Use "import.meta.env" instead of "process.env").`; } createNewErrorOverlay({ title: 'Unhandled Runtime Error', fileLoc, errorMessage: errorMessage, errorStackTrace: event.error ? event.error.stack : undefined, }); }); ================================================ FILE: snowpack/assets/hmr-error-overlay.js ================================================ /* This license applies to parts of this file originating from the https://github.com/vercel/next.js repository: The MIT License (MIT) Copyright (c) 2020 Vercel, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /* Background: This file was copied from the rendered HTML output of the nextjs-error-overlay package / component. The source component was authored for React & JSX which we didn't want to add as dependencies, so we grab the output itself here. */ const ERROR_OVERLAY_TEMPLATE = `

Error

Source

Loading...


Loading...
`; const template = document.createElement('template'); template.innerHTML = ERROR_OVERLAY_TEMPLATE; customElements.define( 'hmr-error-overlay', class HmrErrorOverlay extends HTMLElement { constructor({title, errorMessage, fileLoc, errorStackTrace}) { super(); this.title = title; this.errorMessage = errorMessage; this.fileLoc = fileLoc; this.errorStackTrace = errorStackTrace; this.sr = this.attachShadow({mode: 'open'}); this.sr.appendChild(template.content.cloneNode(true)); this.close = this.close.bind(this); } connectedCallback() { this.sr.getElementById('close-button').addEventListener('click', this.close); this.sr.querySelector('[data-nextjs-dialog-backdrop]').addEventListener('click', this.close); this.sr.getElementById('nextjs__container_errors_label').innerText = this.title; this.sr.getElementById('nextjs__container_errors_desc').innerText = this.errorMessage; if (this.fileLoc) { this.sr.getElementById('error-file-loc').innerText = this.fileLoc; } else { this.sr.getElementById('error-file-loc').innerText = 'No source file.'; } if (this.errorStackTrace) { this.sr.querySelector('pre').innerText = this.errorStackTrace; } else { this.sr.querySelector('pre').style.display = 'none'; } } disconnectedCallback() { this.sr.getElementById('close-button').removeEventListener('click', this.close); this.sr .querySelector('[data-nextjs-dialog-backdrop]') .removeEventListener('click', this.close); } close() { this.parentNode.removeChild(this); } _watchEscape(event) { if (event.key === 'Escape') { this.close(); } } }, ); ================================================ FILE: snowpack/assets/openChrome.appleScript ================================================ (* Copyright (c) 2015-present, Facebook, Inc. This source code is licensed under the MIT license found in the LICENSE file in the root directory of this source tree. *) property targetTab: null property targetTabIndex: -1 property targetWindow: null on run argv set theURL to item 1 of argv tell application "Chrome" if (count every window) = 0 then make new window end if -- 1: Looking for tab running debugger -- then, Reload debugging tab if found -- then return set found to my lookupTabWithUrl(theURL) if found then set targetWindow's active tab index to targetTabIndex tell targetTab to reload tell targetWindow to activate set index of targetWindow to 1 return end if -- 2: Looking for Empty tab -- In case debugging tab was not found -- We try to find an empty tab instead set found to my lookupTabWithUrl("chrome://newtab/") if found then set targetWindow's active tab index to targetTabIndex set URL of targetTab to theURL tell targetWindow to activate return end if -- 3: Create new tab -- both debugging and empty tab were not found -- make a new tab with url tell window 1 activate make new tab with properties {URL:theURL} end tell end tell end run -- Function: -- Lookup tab with given url -- if found, store tab, index, and window in properties -- (properties were declared on top of file) on lookupTabWithUrl(lookupUrl) tell application "Chrome" -- Find a tab with the given url set found to false set theTabIndex to -1 repeat with theWindow in every window set theTabIndex to 0 repeat with theTab in every tab of theWindow set theTabIndex to theTabIndex + 1 if (theTab's URL as string) contains lookupUrl then -- assign tab, tab index, and window to properties set targetTab to theTab set targetTabIndex to theTabIndex set targetWindow to theWindow set found to true exit repeat end if end repeat if found then exit repeat end if end repeat end tell return found end lookupTabWithUrl ================================================ FILE: snowpack/assets/require-or-import.js ================================================ 'use strict'; const {pathToFileURL} = require('url'); const NATIVE_REQUIRE = eval('require'); const NATIVE_IMPORT = (filepath) => import(filepath); const r = require('resolve'); /** * A utility function to use Node's native `require` or dynamic `import` to load CJS or ESM files * @param {string} filepath */ module.exports = async function requireOrImport(filepath, {from = process.cwd()} = {}) { return new Promise((resolve, reject) => { if (filepath.startsWith('node:')) { return NATIVE_IMPORT(filepath).then( (mdl) => resolve(mdl), (err) => reject(err), ); } // Resolve path based on `from` const resolvedPath = r.sync(filepath, { basedir: from, }); try { const mdl = NATIVE_REQUIRE(resolvedPath); // Add a `default` property on a CommonJS export so that it can be accessed from import statements. // This is set to enumerable: false to make it hidden(ish) to code. if (mdl && !('default' in mdl)) { Object.defineProperty(mdl, 'default', { configurable: true, enumerable: false, writable: true, value: mdl, }); } resolve(mdl); } catch (e) { if (e instanceof SyntaxError && /export|import/.test(e.message)) { console.error( `Failed to load "${filepath}"!\nESM format is not natively supported in "node@${process.version}".\nPlease use CommonJS or upgrade to an LTS version of node above "node@12.17.0".`, ); } else if (e.code === 'ERR_REQUIRE_ESM') { const url = pathToFileURL(resolvedPath); return NATIVE_IMPORT(url).then( (mdl) => resolve(mdl.default ? mdl.default : mdl), (err) => reject(err), ); } try { return NATIVE_IMPORT(pathToFileURL(resolvedPath)).then( (mdl) => resolve(mdl.default ? mdl.default : mdl), (err) => reject(err), ); } catch (e) { reject(e); } } }); }; ================================================ FILE: snowpack/assets/snowpack-init-file.js ================================================ // Snowpack Configuration File // See all supported options: https://www.snowpack.dev/reference/configuration /** @type {import("snowpack").SnowpackUserConfig } */ module.exports = { mount: { /* ... */ }, plugins: [ /* ... */ ], packageOptions: { /* ... */ }, devOptions: { /* ... */ }, buildOptions: { /* ... */ }, }; ================================================ FILE: snowpack/index.bin.js ================================================ #!/usr/bin/env node 'use strict'; const ver = process.versions.node; const majorVer = parseInt(ver.split('.')[0], 10); if (majorVer < 10) { console.error('Node version ' + ver + ' is not supported, please use Node.js 10.0 or higher.'); process.exit(1); } const cli = require('./lib/cjs/index.js'); const run = cli.run || cli.cli || cli.default; run(process.argv).catch(function (error) { console.error(` ${error.stack || error.message || error} `); process.exit(1); }); ================================================ FILE: snowpack/package.json ================================================ { "name": "snowpack", "version": "3.8.8", "description": "The ESM-powered frontend build tool. Fast, lightweight, unbundled.", "author": "Fred K. Schott ", "license": "MIT", "keywords": [ "bundle", "build", "build tool", "web", "esm", "esbuild", "wasm" ], "repository": { "type": "git", "url": "https://github.com/withastro/snowpack.git" }, "publishConfig": { "access": "public" }, "scripts": { "build": "tsc --noUnusedLocals false --noUnusedParameters false && tsc -p tsconfig.cjs.json", "build:watch": "tsc --watch -p tsconfig.cjs.json", "lint": "tsc --noEmit" }, "engines": { "node": ">=10.19.0" }, "types": "./lib/cjs/index.d.ts", "main": "./lib/cjs/index.js", "bin": { "sp": "./index.bin.js", "snowpack": "./index.bin.js" }, "files": [ "assets", "lib", "index.bin.js" ], "dependencies": { "@npmcli/arborist": "^2.6.4", "bufferutil": "^4.0.2", "cachedir": "^2.3.0", "cheerio": "1.0.0-rc.10", "chokidar": "^3.4.0", "cli-spinners": "^2.5.0", "compressible": "^2.0.18", "deepmerge": "^4.2.2", "default-browser-id": "^2.0.0", "detect-port": "^1.3.0", "es-module-lexer": "^0.3.24", "esbuild": "~0.9.0", "esinstall": "^1.1.7", "estree-walker": "^2.0.2", "etag": "^1.8.1", "execa": "^5.1.1", "fdir": "^5.0.0", "find-cache-dir": "^3.3.1", "find-up": "^5.0.0", "glob": "^7.1.7", "httpie": "^1.1.2", "is-plain-object": "^5.0.0", "is-reference": "^1.2.1", "isbinaryfile": "^4.0.6", "jsonschema": "~1.2.5", "kleur": "^4.1.1", "magic-string": "^0.25.7", "meriyah": "^3.1.6", "mime-types": "^2.1.26", "mkdirp": "^1.0.3", "npm-run-path": "^4.0.1", "open": "^8.2.1", "pacote": "^11.3.4", "periscopic": "^2.0.3", "picomatch": "^2.3.0", "postcss": "^8.3.5", "postcss-modules": "^4.0.0", "resolve": "^1.20.0", "resolve-from": "^5.0.0", "rimraf": "^3.0.0", "rollup": "~2.37.1", "signal-exit": "^3.0.3", "skypack": "^0.3.2", "slash": "~3.0.0", "source-map": "^0.7.3", "strip-ansi": "^6.0.0", "strip-comments": "^2.0.1", "utf-8-validate": "^5.0.3", "ws": "^7.3.0", "yargs-parser": "^20.0.0" }, "devDependencies": { "@types/cheerio": "^0.22.29" }, "optionalDependencies": { "fsevents": "^2.3.2" } } ================================================ FILE: snowpack/src/build/build-import-proxy.ts ================================================ import path from 'path'; import {logger} from '../logger'; import {SnowpackConfig} from '../types'; import { appendHtmlToHead, hasExtension, spliceString, HMR_CLIENT_CODE, HMR_OVERLAY_CODE, } from '../util'; import {generateSRI} from './import-sri'; import {scanImportGlob} from '../scan-import-glob'; import {cssModuleJSON} from './import-css'; export const SRI_CLIENT_HMR_SNOWPACK = generateSRI(Buffer.from(HMR_CLIENT_CODE)); export const SRI_ERROR_HMR_SNOWPACK = generateSRI(Buffer.from(HMR_OVERLAY_CODE)); const importMetaRegex = /import\s*\.\s*meta/; export function getMetaUrlPath(urlPath: string, config: SnowpackConfig): string { return path.posix.normalize(path.posix.join('/', config.buildOptions.metaUrlPath, urlPath)); } export function wrapImportMeta({ code, hmr, env, config, }: { code: string; hmr: boolean; env: boolean; config: SnowpackConfig; }) { // Create Regex expressions here, since global regex has per-string state const importMetaHotRegex = /import\s*\.\s*meta\s*\.\s*hot/g; const importMetaEnvRegex = /import\s*\.\s*meta\s*\.\s*env/g; // Optimize: replace direct references to `import.meta.hot` by inlining undefined. // Do this first so that we can bail out in the next `import.meta` test. if (!hmr) { code = code.replace(importMetaHotRegex, 'undefined /* [snowpack] import.meta.hot */ '); } if (!importMetaRegex.test(code)) { return code; } let hmrSnippet = ``; if (hmr) { hmrSnippet = `import * as __SNOWPACK_HMR__ from '${getMetaUrlPath( 'hmr-client.js', config, )}';\nimport.meta.hot = __SNOWPACK_HMR__.createHotContext(import.meta.url);\n`; } let envSnippet = ``; if (env) { envSnippet = `import * as __SNOWPACK_ENV__ from '${getMetaUrlPath('env.js', config)}';\n`; // Optimize any direct references `import.meta.env` by inlining the ref code = code.replace(importMetaEnvRegex, '__SNOWPACK_ENV__'); // If we still detect references to `import.meta`, assign `import.meta.env` to be safe if (importMetaRegex.test(code)) { envSnippet += `import.meta.env = __SNOWPACK_ENV__;\n`; } } return hmrSnippet + envSnippet + '\n' + code; } export function wrapHtmlResponse({ code, hmr, hmrPort, isDev, config, mode, }: { code: string; hmr: boolean; hmrPort?: number; isDev: boolean; config: SnowpackConfig; mode: 'development' | 'production'; }) { // replace %PUBLIC_URL% (along with surrounding slashes, if any) code = code.replace(/\/?%PUBLIC_URL%\/?/g, isDev ? '/' : config.buildOptions.baseUrl); // replace %MODE% code = code.replace(/%MODE%/g, mode); // replace any %SNOWPACK_PUBLIC_*% const snowpackPublicEnv = getSnowpackPublicEnvVariables(); code = code.replace(/%SNOWPACK_PUBLIC_.+?%/gi, (match: string) => { const envVariableName = match.slice(1, -1); if (envVariableName in snowpackPublicEnv) { return snowpackPublicEnv[envVariableName] || ''; } logger.warn(`Environment variable "${envVariableName}" is not set`); return match; }); // replace any env variables defined in the config for (const envVar in config.env) { const matcher = new RegExp(`%${envVar}%`, 'g'); const val = config.env[envVar] as string; code = code.replace(matcher, val); } // Full Page Transformations: Only full page responses should get these transformations. // Any code not containing `` is assumed to be an HTML fragment. const isFullPage = code.trim().toLowerCase().startsWith(''); if (hmr && !isFullPage && !config.buildOptions.htmlFragments) { throw new Error(`HTML fragment found! HTML fragments (files not starting with "") are not transformed like full HTML pages. Add the missing doctype, or set buildOptions.htmlFragments=true if HTML fragments are expected.`); } if (hmr && isFullPage) { let hmrScript = ``; if (hmrPort) { hmrScript += `\n`; } hmrScript += ``; if (config.devOptions.hmrErrorOverlay) { hmrScript += ``; } code = appendHtmlToHead(code, hmrScript); } return code; } function generateJsonImportProxy({ code, hmr, config, }: { code: string; hmr: boolean; config: SnowpackConfig; }) { const jsonImportProxyCode = `let json = ${JSON.stringify(JSON.parse(code))}; export default json;`; return wrapImportMeta({code: jsonImportProxyCode, hmr, env: false, config}); } function generateCssImportProxy({ code, hmr, config, }: { code: string; hmr: boolean; config: SnowpackConfig; }) { const cssImportProxyCode = `// [snowpack] add styles to the page (skip if no document exists) if (typeof document !== 'undefined') {${ hmr ? ` import.meta.hot.accept(); import.meta.hot.dispose(() => { document.head.removeChild(styleEl); });\n` : '' } const code = ${JSON.stringify(code)}; const styleEl = document.createElement("style"); const codeEl = document.createTextNode(code); styleEl.type = 'text/css'; styleEl.appendChild(codeEl); document.head.appendChild(styleEl); }`; return wrapImportMeta({code: cssImportProxyCode, hmr, env: false, config}); } function createImportGlobValue(importGlob, i: number): {value: string; imports: string} { let value: string; let imports: string; if (importGlob.isEager) { value = `{\n${importGlob.resolvedImports .map( (spec: string, j: number, {length: len}) => `\t"${spec}": __glob__${i}_${j}${j === len - 1 ? '' : ','}`, ) .join('\n')}\n}`; imports = importGlob.resolvedImports .map((spec: string, j: number) => `import * as __glob__${i}_${j} from '${spec}';`) .join('\n'); } else { value = `{\n${importGlob.resolvedImports .map( (spec: string, j: number, {length: len}) => `\t"${spec}": () => import("${spec}")${j === len - 1 ? '' : ','}`, ) .join('\n')}\n}`; imports = ''; } return {value, imports}; } export async function transformGlobImports({ contents: _code, resolveImportGlobSpecifier = async (i) => [i], }: { contents: string; resolveImportGlobSpecifier: any; }) { const importGlobs = scanImportGlob(_code); let rewrittenCode = _code; const resolvedImportGlobs = await Promise.all( importGlobs.reverse().map(({glob, ...importGlob}) => { return resolveImportGlobSpecifier(glob).then((resolvedImports) => ({ ...importGlob, glob, resolvedImports, })); }), ); resolvedImportGlobs.forEach((importGlob, i) => { const {value, imports} = createImportGlobValue(importGlob, i); rewrittenCode = spliceString(rewrittenCode, value, importGlob.start, importGlob.end); if (imports) { rewrittenCode = `${imports}\n${rewrittenCode}`; } }); return rewrittenCode; } async function generateCssModuleImportProxy({ url, code, hmr, config, }: { url: string; code: string; hmr: boolean; config: SnowpackConfig; }) { const reqUrl = url.replace(new RegExp(`^${config.buildOptions.baseUrl}`), '/'); // note: in build, buildOptions.baseUrl gets prepended. Remove that for looking up CSS Module code return ` export let code = ${JSON.stringify(code)}; let json = ${cssModuleJSON(reqUrl)}; export default json; ${ hmr ? ` import * as __SNOWPACK_HMR_API__ from '${getMetaUrlPath('hmr-client.js', config)}'; import.meta.hot = __SNOWPACK_HMR_API__.createHotContext(import.meta.url);\n` : `` } // [snowpack] add styles to the page (skip if no document exists) if (typeof document !== 'undefined') {${ hmr ? ` import.meta.hot.dispose(() => { document && document.head.removeChild(styleEl); });\n` : `` } const styleEl = document.createElement("style"); const codeEl = document.createTextNode(code); styleEl.type = 'text/css'; styleEl.appendChild(codeEl); document.head.appendChild(styleEl); }`; } function generateDefaultImportProxy(url: string) { return `export default ${JSON.stringify(url)};`; } export async function wrapImportProxy({ url, code, hmr, config, }: { url: string; code: string | Buffer; hmr: boolean; config: SnowpackConfig; }) { if (hasExtension(url, '.json')) { return generateJsonImportProxy({code: code.toString(), hmr, config}); } if (hasExtension(url, '.css')) { // if proxying a CSS file, remove its source map (the path no longer applies) const sanitized = code.toString().replace(/\/\*#\s*sourceMappingURL=[^/]+\//gm, ''); return hasExtension(url, '.module.css') ? generateCssModuleImportProxy({url, code: sanitized, hmr, config}) : generateCssImportProxy({code: sanitized, hmr, config}); } return generateDefaultImportProxy(url); } export function generateEnvModule({ mode, isSSR, configEnv, }: { mode: SnowpackConfig['mode']; isSSR: boolean; configEnv?: Record; }) { const envObject: Record = { ...getSnowpackPublicEnvVariables(), ...(configEnv ?? {}), MODE: mode, NODE_ENV: mode, SSR: isSSR, }; return Object.entries(envObject) .map(([key, val]) => { return `export const ${key} = ${JSON.stringify(val)};`; }) .join('\n'); } const PUBLIC_ENV_REGEX = /^SNOWPACK_PUBLIC_.+/; function getSnowpackPublicEnvVariables() { const envObject = {...process.env}; for (const env of Object.keys(envObject)) { if (!PUBLIC_ENV_REGEX.test(env)) { delete envObject[env]; } } return envObject; } ================================================ FILE: snowpack/src/build/build-pipeline.ts ================================================ import path from 'path'; import {RawSourceMap, SourceMapConsumer, SourceMapGenerator} from 'source-map'; import url from 'url'; import {validatePluginLoadResult} from '../config'; import {logger} from '../logger'; import {PluginTransformResult, SnowpackBuildMap, SnowpackConfig} from '../types'; import {getExtension, readFile, removeExtension} from '../util'; import {cssModules, needsCSSModules} from './import-css'; export interface BuildFileOptions { isDev: boolean; isSSR: boolean; isPackage: boolean; isHmrEnabled: boolean; config: SnowpackConfig; } /** * Build Plugin First Pass: If a plugin defines a * `resolve` object, check it against the current * file's extension. If it matches, call the load() * functon and return it's result. * * If no match is found, fall back to just reading * the file from disk and return it. */ async function runPipelineLoadStep( srcPath: string, {isDev, isSSR, isPackage, isHmrEnabled, config}: BuildFileOptions, ): Promise { const srcExt = getExtension(srcPath); const result: SnowpackBuildMap = {}; for (const step of config.plugins) { if (!step.resolve || !step.resolve.input.some((ext) => srcPath.endsWith(ext))) { continue; } if (!step.load) { continue; } // v3.1: Some plugins break when run on node_modules/. This fix is in place until the plugins themselves have a chance to update. if (step.name.endsWith('@prefresh/snowpack/dist/index.js') && isPackage) { continue; } try { const debugPath = path.relative(config.root, srcPath); logger.debug(`load() starting… [${debugPath}]`, {name: step.name}); const stepResult = await step.load({ fileExt: srcExt, filePath: srcPath, isDev, isSSR, isPackage, isHmrEnabled, }); logger.debug(`✔ load() success [${debugPath}]`, {name: step.name}); validatePluginLoadResult(step, stepResult); if (typeof stepResult === 'string' || Buffer.isBuffer(stepResult)) { const mainOutputExt = step.resolve.output[0]; result[mainOutputExt] = {code: stepResult}; } else if (stepResult && typeof stepResult === 'object') { Object.keys(stepResult).forEach((ext) => { const output = stepResult[ext]; // normalize to {code, map} format if (typeof output === 'string' || Buffer.isBuffer(output)) { result[ext] = {code: output}; } else if (output) { result[ext] = output; } // ensure source maps are strings (it’s easy for plugins to pass back a JSON object) if (result[ext].map && typeof result[ext].map === 'object') { result[ext].map = JSON.stringify(stepResult[ext].map); } // if source maps disabled, don’t return any if (!config.buildOptions.sourcemap) result[ext].map = undefined; }); break; } } catch (err) { // Attach metadata detailing where the error occurred. err.__snowpackBuildDetails = {name: step.name, step: 'load'}; throw err; } } // handle CSS Modules, after plugins run if (needsCSSModules(srcPath)) { let contents: string = result['.css'] ? (result['.css'].code as string) : ((await readFile(srcPath)) as string); if (contents) { // for CSS Modules URLs, we only need the destination URL (POSIX-style) let url = srcPath; for (const dir in config.mount) { if (srcPath.startsWith(dir)) { url = srcPath .replace(dir, config.mount[dir].url) .replace(/\\/g, '/') .replace(/\/+/g, '/') .replace(/\.(scss|sass)$/i, '.css'); break; } } const {css, json} = await cssModules({contents, url}); result['.css'] = {...(result['.css'] || {}), code: css}; result['.json'] = {code: JSON.stringify(json)}; } } // if no result was generated, return file as-is if (!Object.keys(result).length) { return { [srcExt]: { code: await readFile(srcPath), }, }; } return result; } async function composeSourceMaps( id: string, base: string | RawSourceMap, derived: string | RawSourceMap, ): Promise { const [baseMap, transformedMap] = await Promise.all([ new SourceMapConsumer(base), new SourceMapConsumer(derived), ]); try { const generator = SourceMapGenerator.fromSourceMap(transformedMap); generator.applySourceMap(baseMap, id); return generator.toString(); } finally { baseMap.destroy(); transformedMap.destroy(); } } /** * Build Plugin Second Pass: If a plugin defines a * transform() method,call it. Transform cannot change * the file extension, and was designed to run on * every file type and return null/undefined if no * change needed. */ async function runPipelineTransformStep( output: SnowpackBuildMap, srcPath: string, {isDev, isHmrEnabled, isPackage, isSSR, config}: BuildFileOptions, ): Promise { const rootFilePath = removeExtension(srcPath, getExtension(srcPath)); const rootFileName = path.basename(rootFilePath); for (const step of config.plugins) { if (!step.transform) { continue; } // v3.1: Some plugins break when run on node_modules/. This fix is in place until the plugins themselves have a chance to update. if (step.name.endsWith('@prefresh/snowpack/dist/index.js') && isPackage) { continue; } try { for (const destExt of Object.keys(output)) { const destBuildFile = output[destExt]; const {code} = destBuildFile; const fileName = rootFileName + destExt; const filePath = rootFilePath + destExt; const debugPath = path.relative(config.root, filePath); logger.debug(`transform() starting… [${debugPath}]`, {name: step.name}); const result = await step.transform({ contents: code, isDev, isPackage, fileExt: destExt, id: filePath, srcPath, // @ts-ignore: Deprecated filePath: fileName, // @ts-ignore: Deprecated urlPath: `./${path.basename(rootFileName + destExt)}`, isHmrEnabled, isSSR, }); logger.debug(`✔ transform() success [${debugPath}]`, {name: step.name}); if (typeof result === 'string' || Buffer.isBuffer(result)) { // V2 API, simple string variant output[destExt].code = result; output[destExt].map = undefined; } else if (result && typeof result === 'object') { // V2 API, structured result variant const contents = (result as PluginTransformResult).contents || (result as any).result; if (contents) { output[destExt].code = contents; const map = (result as PluginTransformResult).map; let outputMap: string | undefined = undefined; if (map && config.buildOptions.sourcemap) { // if source maps disabled, don’t return any if (output[destExt].map) { outputMap = await composeSourceMaps(filePath, output[destExt].map!, map); } else { outputMap = typeof map === 'object' ? JSON.stringify(map) : map; } } output[destExt].map = outputMap; } } } } catch (err) { // Attach metadata detailing where the error occurred. err.__snowpackBuildDetails = {name: step.name, step: 'transform'}; throw err; } } return output; } export async function runPipelineOptimizeStep( buildDirectory: string, {config}: {config: SnowpackConfig}, ) { for (const step of config.plugins) { if (!step.optimize) { continue; } try { logger.debug('optimize() starting…', {name: step.name}); await step.optimize({ buildDirectory, // @ts-ignore: internal API only log: (msg) => { logger.info(msg, {name: step.name}); }, }); logger.debug('✔ optimize() success', {name: step.name}); } catch (err) { logger.error(err.toString() || err, {name: step.name}); process.exit(1); // exit on error } } return null; } export async function runPipelineCleanupStep({plugins}: SnowpackConfig) { for (const step of plugins) { if (!step.cleanup) { continue; } await step.cleanup(); } } /** Core Snowpack file pipeline builder */ export async function buildFile( srcURL: URL, buildFileOptions: BuildFileOptions, ): Promise { // Pass 1: Find the first plugin to load this file, and return the result const loadResult = await runPipelineLoadStep(url.fileURLToPath(srcURL), buildFileOptions); // Pass 2: Pass that result through every plugin transform() method. const transformResult = await runPipelineTransformStep( loadResult, url.fileURLToPath(srcURL), buildFileOptions, ); // Return the final build result. return transformResult; } ================================================ FILE: snowpack/src/build/file-builder.ts ================================================ import {InstallTarget} from 'esinstall'; import {promises as fs} from 'fs'; import mkdirp from 'mkdirp'; import path from 'path'; import url from 'url'; import {EsmHmrEngine} from '../hmr-server-engine'; import { scanCodeImportsExports, transformEsmImports, transformFileImports, } from '../rewrite-imports'; import {matchDynamicImportValue, scanImportsFromFiles} from '../scan-imports'; import {getPackageSource} from '../sources/util'; import { ImportMap, SnowpackBuildMap, SnowpackBuildResultFileManifest, SnowpackBuiltFile, SnowpackConfig, } from '../types'; import { createInstallTarget, isRemoteUrl, relativeURL, removeLeadingSlash, replaceExtension, } from '../util'; import { getMetaUrlPath, SRI_CLIENT_HMR_SNOWPACK, SRI_ERROR_HMR_SNOWPACK, transformGlobImports, wrapHtmlResponse, wrapImportMeta, wrapImportProxy, } from './build-import-proxy'; import {buildFile} from './build-pipeline'; import {getUrlsForFile} from './file-urls'; import {createImportResolver, createImportGlobResolver} from './import-resolver'; /** * FileBuilder - This class is responsible for building a file. It is broken into * individual stages so that the entire application build process can be tackled * in stages (build -> resolve -> get response). */ export class FileBuilder { buildOutput: SnowpackBuildMap = {}; resolvedOutput: SnowpackBuildMap = {}; isDev: boolean; isHMR: boolean; isSSR: boolean; buildPromise: Promise | undefined; readonly loc: string; readonly urls: string[]; readonly config: SnowpackConfig; hmrEngine: EsmHmrEngine | null = null; constructor({ loc, isDev, isHMR, isSSR, config, hmrEngine, }: { loc: string; isDev: boolean; isHMR: boolean; isSSR: boolean; config: SnowpackConfig; hmrEngine?: EsmHmrEngine | null; }) { this.loc = loc; this.isDev = isDev; this.isHMR = isHMR; this.isSSR = isSSR; this.config = config; this.hmrEngine = hmrEngine || null; const urls = getUrlsForFile(loc, config); if (!urls) { throw new Error(`No mounted URLs configured for file: ${loc}`); } this.urls = urls; } private verifyRequestFromBuild(type: string): SnowpackBuiltFile | undefined { const possibleExtensions = this.urls.map((url) => path.extname(url)); if (!possibleExtensions.includes(type)) throw new Error( `${this.loc} - Requested content "${type}" but only built ${possibleExtensions.join(', ')}`, ); return this.resolvedOutput[type]; } /** * Resolve Imports: Resolved imports are based on the state of the file * system, so they can't be cached long-term with the build. */ async resolveImports( isResolve: boolean, hmrParam?: string | false, importMap?: ImportMap, ): Promise { const urlPathDirectory = path.posix.dirname(this.urls[0]!); const pkgSource = getPackageSource(this.config); const resolvedImports: InstallTarget[] = []; for (const [type, outputResult] of Object.entries(this.buildOutput)) { if (!(type === '.js' || type === '.html' || type === '.css')) { continue; } let contents = typeof outputResult.code === 'string' ? outputResult.code : outputResult.code.toString('utf8'); // Handle attached CSS. if (type === '.js' && this.buildOutput['.css']) { const relativeCssImport = `./${replaceExtension( path.posix.basename(this.urls[0]!), '.js', '.css', )}`; contents = `import '${relativeCssImport}';\n` + contents; } // Finalize the response contents = this.finalizeResult(type, contents); // const resolveImportGlobSpecifier = createImportGlobResolver({ fileLoc: this.loc, config: this.config, }); // resolve all imports const resolveImportSpecifier = createImportResolver({ fileLoc: this.loc, config: this.config, }); const resolveImport = async (spec) => { // Ignore packages marked as external if (this.config.packageOptions.external?.includes(spec)) { return spec; } if (isRemoteUrl(spec)) { return spec; } // Try to resolve the specifier to a known URL in the project let resolvedImportUrl = resolveImportSpecifier(spec); // Handle a package import if (!resolvedImportUrl) { try { return await pkgSource.resolvePackageImport(spec, { importMap: importMap || (isResolve ? undefined : {imports: {}}), }); } catch (err) { if (!isResolve && /not included in import map./.test(err.message)) { return spec; } throw err; } } return resolvedImportUrl || spec; }; const scannedImports = await scanImportsFromFiles( [ { baseExt: type, root: this.config.root, locOnDisk: this.loc, contents, }, ], this.config, ); contents = await transformGlobImports({contents, resolveImportGlobSpecifier}); contents = await transformFileImports({type, contents}, async (spec) => { let resolvedImportUrl = await resolveImport(spec); // Handle normal "./" & "../" import specifiers const importExtName = path.posix.extname(resolvedImportUrl); const isProxyImport = importExtName && importExtName !== '.js' && importExtName !== '.mjs'; const isAbsoluteUrlPath = path.posix.isAbsolute(resolvedImportUrl); if (isAbsoluteUrlPath) { if (isResolve && this.config.buildOptions.resolveProxyImports && isProxyImport) { resolvedImportUrl = resolvedImportUrl + '.proxy.js'; } resolvedImports.push(createInstallTarget(resolvedImportUrl)); } else { resolvedImports.push( ...scannedImports .filter(({specifier}) => specifier === spec) .map((installTarget) => { installTarget.specifier = resolvedImportUrl; return installTarget; }), ); } if (isAbsoluteUrlPath) { // When dealing with an absolute import path, we need to honor the baseUrl // proxy modules may attach code to the root HTML (like style) so don't resolve resolvedImportUrl = relativeURL(urlPathDirectory, resolvedImportUrl); } return resolvedImportUrl; }); // This is a hack since we can't currently scan "script" `src=` tags as imports. // Either move these to inline JavaScript in the script body, or add support for // `script.src=` and `link.href` scanning & resolving in transformFileImports(). if (type === '.html' && this.isHMR) { if (contents.includes(SRI_CLIENT_HMR_SNOWPACK)) { resolvedImports.push(createInstallTarget(getMetaUrlPath('hmr-client.js', this.config))); } if (contents.includes(SRI_ERROR_HMR_SNOWPACK)) { resolvedImports.push( createInstallTarget(getMetaUrlPath('hmr-error-overlay.js', this.config)), ); } } if (type === '.js' && hmrParam) { contents = await transformEsmImports(contents as string, (imp) => { const importUrl = path.posix.resolve(urlPathDirectory, imp); const node = this.hmrEngine?.getEntry(importUrl); if (node && node.needsReplacement) { this.hmrEngine?.markEntryForReplacement(node, false); return `${imp}?${hmrParam}`; } return imp; }); } if (type === '.js') { const isHmrEnabled = contents.includes('import.meta.hot'); const rawImports = await scanCodeImportsExports(contents); const resolvedImports = rawImports.map((imp) => { let spec = contents.substring(imp.s, imp.e).replace(/(\/|\\)+$/, ''); if (imp.d > -1) { spec = matchDynamicImportValue(spec) || ''; } spec = spec.replace(/\?mtime=[0-9]+$/, ''); return path.posix.resolve(urlPathDirectory, spec); }); this.hmrEngine?.setEntry(this.urls[0], resolvedImports, isHmrEnabled); } // Update the output with the new resolved imports this.resolvedOutput[type].code = contents; this.resolvedOutput[type].map = undefined; } return resolvedImports; } /** * Given a file, build it. Building a file sends it through our internal * file builder pipeline, and outputs a build map representing the final * build. A Build Map is used because one source file can result in multiple * built files (Example: .svelte -> .js & .css). */ async build(isStatic: boolean) { if (this.buildPromise) { return this.buildPromise; } const fileBuilderPromise = (async () => { if (isStatic) { return { [path.extname(this.loc)]: { code: await fs.readFile(this.loc), map: undefined, }, }; } const builtFileOutput = await buildFile(url.pathToFileURL(this.loc), { config: this.config, isDev: this.isDev, isSSR: this.isSSR, isPackage: false, isHmrEnabled: this.isHMR, }); return builtFileOutput; })(); this.buildPromise = fileBuilderPromise; try { this.resolvedOutput = {}; this.buildOutput = await fileBuilderPromise; for (const [outputKey, {code, map}] of Object.entries(this.buildOutput)) { this.resolvedOutput[outputKey] = {code, map}; } } finally { this.buildPromise = undefined; } } private finalizeResult(type: string, content: string): string { // Wrap the response. switch (type) { case '.html': { content = wrapHtmlResponse({ code: content as string, hmr: this.isHMR, hmrPort: this.hmrEngine ? this.hmrEngine.port : undefined, isDev: this.isDev, config: this.config, mode: this.isDev ? 'development' : 'production', }); break; } case '.css': { break; } case '.js': { content = wrapImportMeta({ code: content as string, env: true, hmr: this.isHMR, config: this.config, }); } break; } // Return the finalized response. return content; } getResult(type: string): string | Buffer | undefined { const result = this.verifyRequestFromBuild(type); if (result) { // TODO: return result.map return result.code; } } getSourceMap(type: string): string | undefined { return this.resolvedOutput[type].map; } async getProxy(_url: string, type: string) { const code = this.resolvedOutput[type].code; const url = this.isDev ? _url : this.config.buildOptions.baseUrl + removeLeadingSlash(_url); return await wrapImportProxy({url, code, hmr: this.isHMR, config: this.config}); } async writeToDisk(dir: string, results: SnowpackBuildResultFileManifest) { await mkdirp(path.dirname(path.join(dir, this.urls[0]))); for (const outUrl of this.urls) { const buildOutput = results[outUrl].contents; const encoding = typeof buildOutput === 'string' ? 'utf8' : undefined; await fs.writeFile(path.join(dir, outUrl), buildOutput, encoding); } } } ================================================ FILE: snowpack/src/build/file-urls.ts ================================================ import path from 'path'; import slash from 'slash'; import {MountEntry, SnowpackConfig} from '../types'; import {addExtension, getExtensionMatch, replaceExtension} from '../util'; import {needsCSSModules} from './import-css'; /** * Map a file path to the hosted URL for a given "mount" entry. */ export function getUrlsForFileMount({ fileLoc, mountKey, mountEntry, config, }: { fileLoc: string; mountKey: string; mountEntry: MountEntry; config: SnowpackConfig; }): string[] { const resolvedDirUrl = mountEntry.url === '/' ? '' : mountEntry.url; const mountedUrl = fileLoc.replace(mountKey, resolvedDirUrl).replace(/[/\\]+/g, '/'); if (mountEntry.static) { return [mountedUrl]; } return getBuiltFileUrls(mountedUrl, config); } /** * Map a file path to the hosted URL for a given "mount" entry. */ export function getBuiltFileUrls(filepath: string, config: SnowpackConfig): string[] { const fileName = path.basename(filepath); const extensionMatch = getExtensionMatch(fileName, config._extensionMap); if (!extensionMatch) { // CSS Modules require a special .json mapping here if (needsCSSModules(filepath)) { return [filepath, filepath + '.json']; } // Otherwise, return only the requested file return [filepath]; } const [inputExt, outputExts] = extensionMatch; return outputExts.map((outputExt) => { if (outputExts.length > 1) { return addExtension(filepath, outputExt); } else { return replaceExtension(filepath, inputExt, outputExt); } }); } /** * Get the final, hosted URL path for a given file on disk. */ export function getMountEntryForFile( fileLoc: string, config: SnowpackConfig, ): [string, MountEntry] | null { // PERF: Use `for...in` here instead of the slower `Object.entries()` method // that we use everywhere else, since this function can get called 100s of // times during a build. for (const mountKey in config.mount) { if (!config.mount.hasOwnProperty(mountKey)) { continue; } if (!fileLoc.startsWith(mountKey + path.sep)) { continue; } return [mountKey, config.mount[mountKey]]; } return null; } /** * Get the final, hosted URL path for a given file on disk. */ export function getUrlsForFile(fileLoc: string, config: SnowpackConfig): undefined | string[] { const mountEntryResult = getMountEntryForFile(fileLoc, config); if (!mountEntryResult) { if (!config.workspaceRoot) { return undefined; } const builtEntrypointUrls = getBuiltFileUrls(fileLoc, config); return builtEntrypointUrls.map((u) => path.posix.join( config.buildOptions.metaUrlPath, 'link', slash(path.relative(config.workspaceRoot as string, u)), ), ); } const [mountKey, mountEntry] = mountEntryResult; return getUrlsForFileMount({fileLoc, mountKey, mountEntry, config}); } ================================================ FILE: snowpack/src/build/import-css.ts ================================================ import postCss from 'postcss'; import postCssModules from 'postcss-modules'; import {logger} from '../logger'; const cssModuleNames = new Map(); /** Generate CSS Modules for a given URL */ export async function cssModules({ contents, url, }: { contents: string; url: string; }): Promise<{css: string; json: Record}> { let json: Record = {}; const processor = postCss([ postCssModules({ getJSON: (_, moduleNames) => { json = moduleNames; cssModuleNames.set(url, JSON.stringify(moduleNames)); }, }), ]); const result = await processor.process(contents, {from: url, to: url}); // log any warnings that happened. result .warnings() .forEach((element) => logger.warn(`${url} - ${element.text}`, {name: 'snowpack:cssmodules'})); return { css: result.css, json, }; } /** Return CSS Modules JSON from URL */ export function cssModuleJSON(url: string): string { return cssModuleNames.get(url) || '{}'; } /** Should this file get CSS Modules? */ export function needsCSSModules(url: string): boolean { return ( url.endsWith('.module.css') || url.endsWith('.module.scss') || url.endsWith('.module.sass') ); } ================================================ FILE: snowpack/src/build/import-resolver.ts ================================================ import fs from 'fs'; import path from 'path'; import slash from 'slash'; import {SnowpackConfig} from '../types'; import { addExtension, findMatchingAliasEntry, getExtensionMatch, hasExtension, isRemoteUrl, replaceExtension, } from '../util'; import {getUrlsForFile} from './file-urls'; import glob from 'glob'; /** Perform a file disk lookup for the requested import specifier. */ export function getFsStat(importedFileOnDisk: string): fs.Stats | false { try { return fs.statSync(importedFileOnDisk); } catch (err) { // file doesn't exist, that's fine } return false; } /** Resolve an import based on the state of the file/folder found on disk. */ function resolveSourceSpecifier( lazyFileLoc: string, {parentFile, config}: {parentFile: string; config: SnowpackConfig}, ) { const lazyFileStat = getFsStat(lazyFileLoc); if (lazyFileStat && lazyFileStat.isFile()) { lazyFileLoc = lazyFileLoc; } else if (hasExtension(lazyFileLoc, '.js')) { const tsWorkaroundImportFileLoc = replaceExtension(lazyFileLoc, '.js', '.ts'); if (getFsStat(tsWorkaroundImportFileLoc)) { lazyFileLoc = tsWorkaroundImportFileLoc; } } else if (hasExtension(lazyFileLoc, '.jsx')) { const tsWorkaroundImportFileLoc = replaceExtension(lazyFileLoc, '.jsx', '.tsx'); if (getFsStat(tsWorkaroundImportFileLoc)) { lazyFileLoc = tsWorkaroundImportFileLoc; } } else { // missing extension if (getFsStat(lazyFileLoc + path.extname(parentFile))) { // first, try parent file’s extension lazyFileLoc = lazyFileLoc + path.extname(parentFile); } else { // otherwise, try and match any extension from the extension map for (const [ext, outputExts] of Object.entries(config._extensionMap)) { if (!outputExts.includes('.js')) continue; // only look through .js-friendly extensions if (getFsStat(lazyFileLoc + ext)) { lazyFileLoc = lazyFileLoc + ext; break; } } } if (!path.extname(lazyFileLoc)) { if (lazyFileStat && lazyFileStat.isDirectory()) { // Handle directory imports (ex: "./components" -> "./components/index.js") const trailingSlash = lazyFileLoc.endsWith(path.sep) ? '' : path.sep; lazyFileLoc = lazyFileLoc + trailingSlash + 'index.js'; } else { // Fall back to .js. lazyFileLoc = lazyFileLoc + '.js'; } } } // Transform the file extension (from input to output) const extensionMatch = getExtensionMatch(lazyFileLoc, config._extensionMap); if (extensionMatch) { const [inputExt, outputExts] = extensionMatch; if (outputExts.length > 1) { lazyFileLoc = addExtension(lazyFileLoc, outputExts[0]); } else { lazyFileLoc = replaceExtension(lazyFileLoc, inputExt, outputExts[0]); } } const resolvedUrls = getUrlsForFile(lazyFileLoc, config); return resolvedUrls ? resolvedUrls[0] : resolvedUrls; } /** * Create a import resolver function, which converts any import relative to the given file at "fileLoc" * to a proper URL. Returns false if no matching import was found, which usually indicates a package * not found in the import map. */ export function createImportResolver({fileLoc, config}: {fileLoc: string; config: SnowpackConfig}) { return function importResolver(spec: string): string | false { // Ignore "http://*" imports if (isRemoteUrl(spec)) { return spec; } // Ignore packages marked as external if (config.packageOptions.external?.includes(spec)) { return spec; } if (spec[0] === '/') { return spec; } if (spec[0] === '.') { const importedFileLoc = path.resolve(path.dirname(fileLoc), path.normalize(spec)); return resolveSourceSpecifier(importedFileLoc, {parentFile: fileLoc, config}) || spec; } const aliasEntry = findMatchingAliasEntry(config, spec); if (aliasEntry && (aliasEntry.type === 'path' || aliasEntry.type === 'url')) { const {from, to} = aliasEntry; let result = spec.replace(from, to); if (aliasEntry.type === 'url') { return result; } const importedFileLoc = path.resolve(config.root, result); return resolveSourceSpecifier(importedFileLoc, {parentFile: fileLoc, config}) || spec; } return false; }; } function toPath(url: string) { return url.replace(/\//g, path.sep); } /** * Create a import glob resolver function, which converts any import globs relative to the given file at "fileLoc" * to a local file. These will additionally be transformed by the regular import resolver, so they do not need * to be finalized just yet */ export function createImportGlobResolver({ fileLoc, config, }: { fileLoc: string; config: SnowpackConfig; }) { const rootDir = path.parse(process.cwd()).root; return async function importGlobResolver(spec: string): Promise { let searchSpec = toPath(spec); if (spec.startsWith('/')) { searchSpec = path.join(config.root, spec); } const aliasEntry = findMatchingAliasEntry(config, spec); if (aliasEntry && aliasEntry.type === 'path') { const {from, to} = aliasEntry; searchSpec = searchSpec.replace(from, to); searchSpec = path.resolve(config.root, searchSpec); } if (!searchSpec.startsWith(rootDir) && !searchSpec.startsWith('.')) { throw new Error( `Glob imports must be relative (starting with ".") or absolute (starting with "/", which is treated as relative to project root)`, ); } if (searchSpec.startsWith(rootDir)) { searchSpec = path.resolve(config.root, searchSpec); searchSpec = path.relative(path.dirname(fileLoc), searchSpec); } const resolved = await new Promise((resolve, reject) => glob(searchSpec, {cwd: path.dirname(fileLoc), nodir: true}, (err, matches) => { if (err) { return reject(err); } return resolve(matches); }), ); return resolved .map((fileLoc) => { const normalized = slash(fileLoc); if (normalized.startsWith('.') || normalized.startsWith('/')) return normalized; return `./${normalized}`; }) .filter((_fileLoc) => { // If final import *might* be the same as the source file, double check to avoid importing self const finalImportAbsolute = slash(path.resolve(path.dirname(fileLoc), toPath(_fileLoc))); return slash(finalImportAbsolute) !== slash(fileLoc); }); }; } ================================================ FILE: snowpack/src/build/import-sri.ts ================================================ import {createHash} from 'crypto'; type SupportedSRIAlgorithm = 'sha512' | 'sha384' | 'sha256'; const DEFAULT_CRYPTO_HASH = 'sha384'; const EMPTY_BUFFER = Buffer.from(''); export const generateSRI = ( buffer: Buffer = EMPTY_BUFFER, hashAlgorithm: SupportedSRIAlgorithm = DEFAULT_CRYPTO_HASH, ) => `${hashAlgorithm}-${createHash(hashAlgorithm).update(buffer).digest('base64')}`; ================================================ FILE: snowpack/src/build/optimize.ts ================================================ import cheerio from 'cheerio'; import * as esbuild from 'esbuild'; import {promises as fs, readFileSync, unlinkSync, writeFileSync} from 'fs'; import {fdir} from 'fdir'; import mkdirp from 'mkdirp'; import path from 'path'; import {logger} from '../logger'; import {OptimizeOptions, SnowpackConfig} from '../types'; import { addLeadingSlash, addTrailingSlash, hasExtension, isRemoteUrl, isTruthy, removeLeadingSlash, removeTrailingSlash, deleteFromBuildSafe, } from '../util'; import {getUrlsForFile} from './file-urls'; interface ScannedHtmlEntrypoint { file: string; root: cheerio.Root; getScripts: () => cheerio.Cheerio; getStyles: () => cheerio.Cheerio; getLinks: (rel: 'stylesheet') => cheerio.Cheerio; } // The manifest type is the one from ESBuild, but we might delete the outputs key type SnowpackMetaManifest = Omit & Partial; // We want to output our bundled build directly into our build directory, but esbuild // has a bug where it complains about overwriting source files even when write: false. // We create a fake bundle directory for now. Nothing ever actually gets written here. const FAKE_BUILD_DIRECTORY = path.join(process.cwd(), '~~bundle~~'); const FAKE_BUILD_DIRECTORY_REGEX = /.*\~\~bundle\~\~[\\\/]/; /** Collect deep imports in the given set, recursively. */ function collectDeepImports( config: SnowpackConfig, url: string, manifest: SnowpackMetaManifest, set: Set, ): void { const buildPrefix = removeLeadingSlash(config.buildOptions.out.replace(process.cwd(), '')) .split(path.sep) .join(path.posix.sep); const normalizedUrl = !url.startsWith(buildPrefix) ? path.posix.join(buildPrefix, url) : url; const relativeImportUrl = url.replace(buildPrefix, ''); if (set.has(relativeImportUrl)) { return; } set.add(relativeImportUrl); const manifestEntry = manifest.inputs[normalizedUrl]; if (!manifestEntry) { throw new Error('Not Found in manifest: ' + normalizedUrl); } manifestEntry.imports.forEach(({path}) => collectDeepImports(config, path, manifest, set)); return; } /** * Scan a collection of HTML files for entrypoints. A file is deemed an "html entrypoint" * if it contains an element. This prevents partials from being scanned. */ async function scanHtmlEntrypoints(htmlFiles: string[]): Promise<(ScannedHtmlEntrypoint | null)[]> { return Promise.all( htmlFiles.map(async (htmlFile) => { const code = await fs.readFile(htmlFile, 'utf8'); const root = cheerio.load(code, {decodeEntities: false}); const isHtmlFragment = root.html().startsWith(''); if (isHtmlFragment) { return null; } return { file: htmlFile, root, getScripts: () => root('script[type="module"]'), getStyles: () => root('style'), getLinks: (rel: 'stylesheet') => root(`link[rel="${rel}"]`), }; }), ); } async function extractBaseUrl(htmlData: ScannedHtmlEntrypoint, baseUrl: string): Promise { const {root, getScripts, getLinks} = htmlData; if (!baseUrl || baseUrl === '/') { return; } getScripts().each((_, elem) => { const scriptRoot = root(elem); const scriptSrc = scriptRoot.attr('src'); if (!scriptSrc || !scriptSrc.startsWith(baseUrl)) { return; } scriptRoot.attr('src', addLeadingSlash(scriptSrc.replace(baseUrl, ''))); scriptRoot.attr('snowpack-baseurl', 'true'); }); getLinks('stylesheet').each((_, elem) => { const linkRoot = root(elem); const styleHref = linkRoot.attr('href'); if (!styleHref || !styleHref.startsWith(baseUrl)) { return; } linkRoot.attr('href', addLeadingSlash(styleHref.replace(baseUrl, ''))); linkRoot.attr('snowpack-baseurl', 'true'); }); } async function restitchBaseUrl(htmlData: ScannedHtmlEntrypoint, baseUrl: string): Promise { const {root, getScripts, getLinks} = htmlData; getScripts() .filter('[snowpack-baseurl]') .each((_, elem) => { const scriptRoot = root(elem); const scriptSrc = scriptRoot.attr('src')!; scriptRoot.attr('src', removeTrailingSlash(baseUrl) + addLeadingSlash(scriptSrc)); scriptRoot.removeAttr('snowpack-baseurl'); }); getLinks('stylesheet') .filter('[snowpack-baseurl]') .each((_, elem) => { const linkRoot = root(elem); const styleHref = linkRoot.attr('href')!; linkRoot.attr('href', removeTrailingSlash(baseUrl) + addLeadingSlash(styleHref)); linkRoot.removeAttr('snowpack-baseurl'); }); } async function extractInlineScripts(htmlData: ScannedHtmlEntrypoint): Promise { const {file, root, getScripts, getStyles} = htmlData; getScripts().each((i, elem) => { const scriptRoot = root(elem); const scriptContent = scriptRoot.contents().text(); if (!scriptContent) { return; } scriptRoot.empty(); writeFileSync(file + `.inline.${i}.js`, scriptContent); scriptRoot.attr('src', `./${path.basename(file)}.inline.${i}.js`); scriptRoot.attr('snowpack-inline', `true`); }); getStyles().each((i, elem) => { const styleRoot = root(elem); const styleContent = styleRoot.contents().text(); if (!styleContent) { return; } styleRoot.after( ``, ); styleRoot.remove(); writeFileSync(file + `.inline.${i}.css`, styleContent); }); } async function restitchInlineScripts(htmlData: ScannedHtmlEntrypoint): Promise { const {file, root, getScripts, getLinks} = htmlData; getScripts() .filter('[snowpack-inline]') .each((_, elem) => { const scriptRoot = root(elem); const scriptFile = path.resolve(file, '..', scriptRoot.attr('src')!); const scriptContent = readFileSync(scriptFile, 'utf8'); scriptRoot.text(scriptContent); scriptRoot.removeAttr('src'); scriptRoot.removeAttr('snowpack-inline'); unlinkSync(scriptFile); }); getLinks('stylesheet') .filter('[snowpack-inline]') .each((_, elem) => { const linkRoot = root(elem); const styleFile = path.resolve(file, '..', linkRoot.attr('href')!); const styleContent = readFileSync(styleFile, 'utf8'); const newStyleEl = root(''); newStyleEl.text(styleContent); linkRoot.after(newStyleEl); linkRoot.remove(); unlinkSync(styleFile); }); } /** Add new bundled CSS files to the HTML entrypoint file, if not already there. */ function addNewBundledCss( htmlData: ScannedHtmlEntrypoint, manifest: SnowpackMetaManifest, baseUrl: string, ): void { if (!manifest.outputs) { return; } for (const key of Object.keys(manifest.outputs)) { if (!hasExtension(key, '.css')) { continue; } const scriptKey = key.replace('.css', '.js'); if (!manifest.outputs[scriptKey]) { continue; } const hasCssImportAlready = htmlData .getLinks('stylesheet') .toArray() .some((v) => { const {attribs} = v as cheerio.TagElement; return attribs && attribs.href.includes(removeLeadingSlash(key)); }); const hasScriptImportAlready = htmlData .getScripts() .toArray() .some((v) => { const {attribs} = v as cheerio.TagElement; return attribs && attribs.src.includes(removeLeadingSlash(scriptKey)); }); if (hasCssImportAlready || !hasScriptImportAlready) { continue; } const linkHref = removeTrailingSlash(baseUrl) + addLeadingSlash(key); htmlData.root('head').append(``); } } /** * Traverse the entrypoint for JS scripts, and add preload links to the HTML entrypoint. */ function preloadEntrypoint( htmlData: ScannedHtmlEntrypoint, manifest: SnowpackMetaManifest, config: SnowpackConfig, ): void { const {root, getScripts} = htmlData; const preloadScripts = getScripts() .map((_, elem) => { const {attribs} = elem as cheerio.TagElement; return attribs.src; }) .get() .filter(isTruthy); const collectedDeepImports = new Set(); for (const preloadScript of preloadScripts) { collectDeepImports(config, preloadScript, manifest, collectedDeepImports); } const baseUrl = config.buildOptions.baseUrl; for (const imp of collectedDeepImports) { const preloadUrl = (baseUrl ? removeTrailingSlash(baseUrl) : '') + addLeadingSlash(imp); root('head').append(``); } } /** * Handle the many different user input formats to return an array of strings. * resolve "auto" mode here. */ async function getEntrypoints( entrypoints: OptimizeOptions['entrypoints'], allBuildFiles: string[], ) { if (entrypoints === 'auto') { // TODO: Filter allBuildFiles by HTML with head & body return allBuildFiles.filter((f) => hasExtension(f, '.html')); } if (Array.isArray(entrypoints)) { return entrypoints; } if (typeof entrypoints === 'function') { return entrypoints({files: allBuildFiles}); } throw new Error('UNEXPECTED ENTRYPOINTS: ' + entrypoints); } /** * Resolve an array of string entrypoints to absolute file paths. Handle * source vs. build directory relative entrypoints here as well. */ async function resolveEntrypoints( entrypoints: string[], cwd: string, buildDirectoryLoc: string, config: SnowpackConfig, ) { return Promise.all( entrypoints.map(async (entrypoint) => { if (path.isAbsolute(entrypoint)) { return entrypoint; } const buildEntrypoint = path.resolve(buildDirectoryLoc, entrypoint); if (await fs.stat(buildEntrypoint).catch(() => null)) { return buildEntrypoint; } const resolvedSourceFile = path.resolve(cwd, entrypoint); let resolvedSourceEntrypoint: string | undefined; if (await fs.stat(resolvedSourceFile).catch(() => null)) { const resolvedSourceUrls = getUrlsForFile(resolvedSourceFile, config); if (resolvedSourceUrls) { resolvedSourceEntrypoint = path.resolve( buildDirectoryLoc, removeLeadingSlash(resolvedSourceUrls[0]), ); if (await fs.stat(resolvedSourceEntrypoint).catch(() => null)) { return resolvedSourceEntrypoint; } } } logger.error(`Error: entrypoint "${entrypoint}" not found in either build or source:`, { name: 'optimize', }); logger.error(` ✘ Build Entrypoint: ${buildEntrypoint}`, {name: 'optimize'}); logger.error( ` ✘ Source Entrypoint: ${resolvedSourceFile} ${ resolvedSourceEntrypoint ? `-> ${resolvedSourceEntrypoint}` : '' }`, {name: 'optimize'}, ); throw new Error(`Optimize entrypoint "${entrypoint}" does not exist.`); }), ); } /** * Process your entrypoints as either all JS or all HTML. If HTML, * scan those HTML files and add a Cheerio-powered root document * so that we can modify the HTML files as we go. */ async function processEntrypoints( originalEntrypointValue: OptimizeOptions['entrypoints'], entrypointFiles: string[], buildDirectoryLoc: string, baseUrl: string, ): Promise<{htmlEntrypoints: null | ScannedHtmlEntrypoint[]; bundleEntrypoints: string[]}> { // If entrypoints are JS: if (entrypointFiles.every((f) => hasExtension(f, '.js'))) { return {htmlEntrypoints: null, bundleEntrypoints: entrypointFiles}; } // If entrypoints are HTML: if (entrypointFiles.every((f) => hasExtension(f, '.html'))) { const rawHtmlEntrypoints = await scanHtmlEntrypoints(entrypointFiles); const htmlEntrypoints = rawHtmlEntrypoints.filter(isTruthy); if ( originalEntrypointValue !== 'auto' && rawHtmlEntrypoints.length !== htmlEntrypoints.length ) { throw new Error('INVALID HTML ENTRYPOINTS: ' + originalEntrypointValue); } htmlEntrypoints.forEach((val) => extractBaseUrl(val, baseUrl)); htmlEntrypoints.forEach(extractInlineScripts); const bundleEntrypoints = Array.from( htmlEntrypoints.reduce((all, val) => { val.getLinks('stylesheet').each((_, elem) => { const {attribs} = elem as cheerio.TagElement; if (!attribs || !attribs.href || isRemoteUrl(attribs.href)) { return; } const resolvedCSS = attribs.href[0] === '/' ? path.resolve(buildDirectoryLoc, removeLeadingSlash(attribs.href)) : path.resolve(val.file, '..', attribs.href); all.add(resolvedCSS); }); val.getScripts().each((_, elem) => { const {attribs} = elem as cheerio.TagElement; if (!attribs.src || isRemoteUrl(attribs.src)) { return; } const resolvedJS = attribs.src[0] === '/' ? path.join(buildDirectoryLoc, removeLeadingSlash(attribs.src)) : path.join(val.file, '..', attribs.src); all.add(resolvedJS); }); return all; }, new Set()), ); return {htmlEntrypoints, bundleEntrypoints}; } // If entrypoints are mixed or neither, throw an error. throw new Error('MIXED ENTRYPOINTS: ' + entrypointFiles); } /** * Run esbuild on the build directory. This is run regardless of bundle=true or false, * since we use the generated manifest in either case. */ async function runEsbuildOnBuildDirectory( bundleEntrypoints: string[], allFiles: string[], config: SnowpackConfig, ): Promise<{manifest: SnowpackMetaManifest; outputFiles: esbuild.OutputFile[]}> { // esbuild requires publicPath to be a remote URL. Only pass to esbuild if baseUrl is remote. let publicPath: string | undefined; if ( config.buildOptions.baseUrl.startsWith('http:') || config.buildOptions.baseUrl.startsWith('https:') || config.buildOptions.baseUrl.startsWith('//') ) { publicPath = config.buildOptions.baseUrl; } const {outputFiles, warnings, metafile} = await esbuild.build({ entryPoints: bundleEntrypoints, outdir: FAKE_BUILD_DIRECTORY, outbase: config.buildOptions.out, write: false, bundle: true, loader: config.optimize!.loader, sourcemap: config.optimize!.sourcemap, splitting: config.optimize!.splitting, format: 'esm', platform: 'browser', metafile: true, publicPath, minify: config.optimize!.minify, target: config.optimize!.target, external: Array.from(new Set(allFiles.map((f) => '*' + path.extname(f)))) .filter((ext) => ext !== '*.js' && ext !== '*.mjs' && ext !== '*.css' && ext !== '*') .concat(config.packageOptions?.external ?? []), charset: 'utf8', }); if (!outputFiles) { throw new Error('EMPTY BUILD'); } if (warnings.length > 0) { console.warn(warnings); } outputFiles.forEach((f) => { f.path = f.path.replace(FAKE_BUILD_DIRECTORY_REGEX, addTrailingSlash(config.buildOptions.out)); }); const manifest = metafile!; if (!config.optimize?.bundle) { delete (manifest as SnowpackMetaManifest).outputs; } else { Object.entries(manifest.outputs).forEach(([f, val]) => { const newKey = f.replace(FAKE_BUILD_DIRECTORY_REGEX, '/'); manifest.outputs[newKey] = val; delete manifest.outputs[f]; }); } logger.debug(`outputFiles: ${JSON.stringify(outputFiles.map((f) => f.path))}`); logger.debug(`manifest: ${JSON.stringify(manifest)}`); return {outputFiles, manifest}; } /** The main optimize function: runs optimization on a build directory. */ export async function runBuiltInOptimize(config: SnowpackConfig) { const originalCwd = process.cwd(); const buildDirectoryLoc = config.buildOptions.out; const options = config.optimize; if (!options) { return; } // * Scan to collect all build files: We'll need this throughout. const allBuildFiles = (await new fdir() .withFullPaths() .crawl(buildDirectoryLoc) .withPromise()) as string[]; // * Resolve and validate your entrypoints: they may be JS or HTML const userEntrypoints = await getEntrypoints(options.entrypoints, allBuildFiles); logger.debug(JSON.stringify(userEntrypoints), {name: 'optimize.entrypoints'}); const resolvedEntrypoints = await resolveEntrypoints( userEntrypoints, originalCwd, buildDirectoryLoc, config, ); logger.debug('(resolved) ' + JSON.stringify(resolvedEntrypoints), {name: 'optimize.entrypoints'}); const {htmlEntrypoints, bundleEntrypoints} = await processEntrypoints( options.entrypoints, resolvedEntrypoints, buildDirectoryLoc, config.buildOptions.baseUrl, ); logger.debug(`htmlEntrypoints: ${JSON.stringify(htmlEntrypoints?.map((f) => f.file))}`); logger.debug(`bundleEntrypoints: ${JSON.stringify(bundleEntrypoints)}`); if ( (!htmlEntrypoints || htmlEntrypoints.length === 0) && bundleEntrypoints.length === 0 && (options.bundle || options.preload) ) { throw new Error( '[optimize] No HTML entrypoints detected. Set "entrypoints" manually if your site HTML is generated outside of Snowpack (SSR, Rails, PHP, etc.).', ); } // * Run esbuild on the entire build directory. Even if you are not writing the result // to disk (bundle: false), we still use the bundle manifest as an in-memory representation // of our import graph, saved to disk. const {manifest, outputFiles} = await runEsbuildOnBuildDirectory( bundleEntrypoints, allBuildFiles, config, ); // * BUNDLE: TRUE - Save the bundle result to the build directory, and clean up to remove all original // build files that now live in the bundles. if (options.bundle) { for (const bundledInput of Object.keys(manifest.inputs)) { const outputKey = path.relative(buildDirectoryLoc, path.resolve(process.cwd(), bundledInput)); if (!manifest.outputs![`/` + outputKey]) { logger.debug(`Removing bundled source file: ${path.resolve(buildDirectoryLoc, outputKey)}`); deleteFromBuildSafe(path.resolve(buildDirectoryLoc, outputKey), config); } } deleteFromBuildSafe( path.resolve( buildDirectoryLoc, removeLeadingSlash(path.posix.join(config.buildOptions.metaUrlPath, 'pkg')), ), config, ); for (const outputFile of outputFiles!) { mkdirp.sync(path.dirname(outputFile.path)); await fs.writeFile(outputFile.path, outputFile.contents); } if (htmlEntrypoints) { for (const htmlEntrypoint of htmlEntrypoints) { addNewBundledCss(htmlEntrypoint, manifest, config.buildOptions.baseUrl); } } } // * BUNDLE: FALSE - Just minifying & transform the CSS & JS files in place. else if (options.minify || options.target) { for (const f of allBuildFiles) { if (['.js', '.css'].includes(path.extname(f))) { let code = await fs.readFile(f, 'utf8'); const minified = await esbuild.transform(code, { sourcefile: path.basename(f), loader: path.extname(f).slice(1) as 'js' | 'css', minify: options.minify, target: options.target, charset: 'utf8', }); code = minified.code; await fs.writeFile(f, code); } } } // * Restitch any inline scripts into HTML entrypoints that had been split out // for the sake of bundling/manifest. if (htmlEntrypoints) { for (const htmlEntrypoint of htmlEntrypoints) { restitchInlineScripts(htmlEntrypoint); } } // * PRELOAD: TRUE - Add preload link elements for each HTML entrypoint, to flatten // and optimize any deep import waterfalls. if (options.preload) { if (options.bundle) { throw new Error('preload is not needed when bundle=true, and cannot be used in combination.'); } if (!htmlEntrypoints || htmlEntrypoints.length === 0) { throw new Error('preload only works with HTML entrypoints.'); } for (const htmlEntrypoint of htmlEntrypoints) { preloadEntrypoint(htmlEntrypoint, manifest, config); } } // * Restitch any inline scripts into HTML entrypoints that had been split out // for the sake of bundling/manifest. if (htmlEntrypoints) { for (const htmlEntrypoint of htmlEntrypoints) { restitchBaseUrl(htmlEntrypoint, config.buildOptions.baseUrl); } } // Write the final HTML entrypoints to disk (if they exist). if (htmlEntrypoints) { for (const htmlEntrypoint of htmlEntrypoints) { await fs.writeFile(htmlEntrypoint.file, htmlEntrypoint.root.html()); } } // Write the final build manifest to disk. if (options.manifest) { await fs.writeFile( path.join(config.buildOptions.out, 'build-manifest.json'), JSON.stringify(manifest), ); } process.chdir(originalCwd); return; } ================================================ FILE: snowpack/src/build/process.ts ================================================ import type {ImportMap, InstallTarget} from 'esinstall'; import type { LoadUrlOptions, CommandOptions, OnFileChangeCallback, SnowpackConfig, SnowpackDevServer, } from '../types'; import * as colors from 'kleur/colors'; import {promises as fs} from 'fs'; import {performance} from 'perf_hooks'; import {fdir} from 'fdir'; import mkdirp from 'mkdirp'; import path from 'path'; import picomatch from 'picomatch'; import slash from 'slash'; import {getUrlsForFile} from './file-urls'; import {runPipelineCleanupStep, runPipelineOptimizeStep} from './build-pipeline'; import {wrapImportProxy} from './build-import-proxy'; import {runBuiltInOptimize} from './optimize'; import {startServer} from '../commands/dev'; import {getPackageSource} from '../sources/util'; import {installPackages} from '../sources/local-install'; import {deleteFromBuildSafe, isPathImport, isRemoteUrl, IS_DOTFILE_REGEX} from '../util'; import {logger} from '../logger'; interface BuildState { commandOptions: CommandOptions; config: SnowpackConfig; // Environments isWatch: boolean; isDev: boolean; isSSR: boolean; isHMR: boolean; // Options // Should the build directory be cleaned first clean: boolean; // State allBareModuleSpecifiers: InstallTarget[]; allFileUrlsUnique: Set; allFileUrlsToProcess: string[]; buildDirectoryLoc: string; devServer: SnowpackDevServer; optimizedImportMap?: ImportMap; } function getIsHmrEnabled(config: SnowpackConfig) { return config.buildOptions.watch && !!config.devOptions.hmr; } /** * Scan a directory and remove any empty folders, recursively. */ async function removeEmptyFolders(directoryLoc: string): Promise { if (!(await fs.stat(directoryLoc)).isDirectory()) { return false; } // If folder is empty, clear it const files = await fs.readdir(directoryLoc); if (files.length === 0) { await fs.rmdir(directoryLoc); return false; } // Otherwise, step in and clean each contained item await Promise.all(files.map((file) => removeEmptyFolders(path.join(directoryLoc, file)))); // After, check again if folder is now empty const afterFiles = await fs.readdir(directoryLoc); if (afterFiles.length == 0) { await fs.rmdir(directoryLoc); } return true; } async function installOptimizedDependencies( installTargets: InstallTarget[], installDest: string, commandOptions: CommandOptions, ) { const baseInstallOptions = { dest: installDest, external: commandOptions.config.packageOptions.external, env: {NODE_ENV: commandOptions.config.mode}, treeshake: commandOptions.config.buildOptions.watch ? false : commandOptions.config.optimize?.treeshake !== false, }; const pkgSource = getPackageSource(commandOptions.config); const installOptions = await pkgSource.modifyBuildInstallOptions( baseInstallOptions, installTargets, ); // 2. Install dependencies, based on the scan of your final build. const installResult = await installPackages({ config: commandOptions.config, isSSR: commandOptions.config.buildOptions.ssr, isDev: false, installTargets, installOptions, }); return installResult; } export async function createBuildState(commandOptions: CommandOptions): Promise { const {config} = commandOptions; const isWatch = !!config.buildOptions.watch; const isDev = !!isWatch; const isSSR = !!config.buildOptions.ssr; const isHMR = getIsHmrEnabled(config); // Seems like maybe we shouldn't be doing this... config.buildOptions.resolveProxyImports = !config.optimize?.bundle; config.devOptions.hmrPort = isHMR ? config.devOptions.hmrPort : undefined; config.devOptions.port = 0; const clean = config.buildOptions.clean; const buildDirectoryLoc = config.buildOptions.out; const devServer = await startServer(commandOptions, {isDev, isWatch, preparePackages: false}); return { commandOptions, config, isDev, isHMR, isSSR, isWatch, clean, buildDirectoryLoc, allBareModuleSpecifiers: [], allFileUrlsUnique: new Set(), allFileUrlsToProcess: [], devServer, }; } export function maybeCleanBuildDirectory(state: BuildState) { const {buildDirectoryLoc} = state; if (state.clean) { deleteFromBuildSafe(buildDirectoryLoc, state.config); } mkdirp.sync(buildDirectoryLoc); } export async function addBuildFiles(state: BuildState, files: string[]) { const {config} = state; const excludeGlobs = [...config.exclude, ...config.testOptions.files]; const foundExcludeMatch = picomatch(excludeGlobs); const mountedNodeModules = Object.keys(config.mount).filter((v) => v.includes('node_modules')); const allFileUrls: string[] = []; for (const f of files) { if (foundExcludeMatch(f)) { const isMounted = mountedNodeModules.find((mountKey) => f.startsWith(mountKey)); if (!isMounted || (isMounted && foundExcludeMatch(f.slice(isMounted.length)))) { continue; } } const fileUrls = getUrlsForFile(f, config)!; allFileUrls.push(...fileUrls); } state.allBareModuleSpecifiers = []; state.allFileUrlsUnique = new Set(allFileUrls); state.allFileUrlsToProcess = [...state.allFileUrlsUnique]; } export async function addBuildFilesFromMountpoints(state: BuildState): Promise { const {config} = state; const possibleFiles: string[] = []; for (const [mountKey, mountEntry] of Object.entries(config.mount)) { logger.debug(`Mounting directory: '${mountKey}' as URL '${mountEntry.url}'`); const allMatchedFiles = (await new fdir() .withFullPaths() .crawl(mountKey) .withPromise()) as string[]; if (mountEntry.dot) { possibleFiles.push(...allMatchedFiles); } else { possibleFiles.push(...allMatchedFiles.filter((f) => !IS_DOTFILE_REGEX.test(slash(f)))); // TODO: use a file URL instead } } addBuildFiles(state, possibleFiles); } type FlushLoadOptions = LoadUrlOptions & {encoding?: undefined}; async function flushFileQueue( state: BuildState, ignorePkg: boolean, loadOptions: FlushLoadOptions, ) { const { config, allFileUrlsUnique, allFileUrlsToProcess, allBareModuleSpecifiers, buildDirectoryLoc, devServer, isHMR, } = state; const pkgUrlPrefix = path.posix.join(config.buildOptions.metaUrlPath, 'pkg/'); logger.debug(`QUEUE: ${allFileUrlsToProcess}`); while (allFileUrlsToProcess.length > 0) { const fileUrl = allFileUrlsToProcess.shift()!; const fileDestinationLoc = path.join(buildDirectoryLoc, fileUrl); logger.debug(`BUILD: ${fileUrl}`); // ignore package URLs when `ignorePkg` is true, EXCEPT proxy imports. Those can sometimes // be added after the intial package scan, depending on how a non-JS package is imported. if (ignorePkg && fileUrl.startsWith(pkgUrlPrefix)) { if (fileUrl.endsWith('.proxy.js')) { const pkgContents = await fs.readFile( path.join(buildDirectoryLoc, fileUrl.replace('.proxy.js', '')), ); const pkgContentsProxy = await wrapImportProxy({ url: fileUrl.replace('.proxy.js', ''), code: pkgContents, hmr: isHMR, config: config, }); await fs.writeFile(fileDestinationLoc, pkgContentsProxy); } continue; } const result = await devServer.loadUrl(fileUrl, loadOptions); if (!result) { // if this URL doesn’t exist, skip to next file (it may be an optional output type, such as .css for .svelte) logger.debug(`BUILD: ${fileUrl} skipped (no output)`); continue; } await mkdirp(path.dirname(fileDestinationLoc)); await fs.writeFile(fileDestinationLoc, result.contents); for (const installTarget of result.imports) { const importedUrl = installTarget.specifier; logger.debug(`ADD: ${importedUrl}`); if (isRemoteUrl(importedUrl)) { // do nothing } else if (isPathImport(importedUrl)) { if (importedUrl[0] === '/') { if (!allFileUrlsUnique.has(importedUrl)) { allFileUrlsUnique.add(importedUrl); allFileUrlsToProcess.push(importedUrl); } } else { logger.warn(`warn: import "${importedUrl}" of "${fileUrl}" could not be resolved.`); } } else { allBareModuleSpecifiers.push(installTarget); } } } } export async function buildFiles(state: BuildState) { const {isSSR, isHMR} = state; logger.info(colors.yellow('! building files...')); const buildStart = performance.now(); await flushFileQueue(state, false, {isSSR, isHMR, isResolve: false}); const buildEnd = performance.now(); logger.info( `${colors.green('✔')} files built. ${colors.dim( `[${((buildEnd - buildStart) / 1000).toFixed(2)}s]`, )}`, ); } export async function buildDependencies(state: BuildState) { const {commandOptions, config, buildDirectoryLoc, isWatch} = state; logger.info(colors.yellow('! building dependencies...')); const packagesStart = performance.now(); if (isWatch) { const pkgSource = getPackageSource(config); await pkgSource.prepare(); } else { const installDest = path.join(buildDirectoryLoc, config.buildOptions.metaUrlPath, 'pkg'); const installResult = await installOptimizedDependencies( [...state.allBareModuleSpecifiers], installDest, commandOptions, ); state.optimizedImportMap = installResult.importMap; } const packagesEnd = performance.now(); logger.info( `${colors.green('✔')} dependencies built. ${colors.dim( `[${((packagesEnd - packagesStart) / 1000).toFixed(2)}s]`, )}`, ); } export async function writeToDisk(state: BuildState) { const {isHMR, isSSR, isWatch} = state; logger.info(colors.yellow('! writing to disk...')); const writeStart = performance.now(); state.allFileUrlsToProcess = [...state.allFileUrlsUnique]; await flushFileQueue(state, !isWatch, { isSSR, isHMR, isResolve: true, importMap: state.optimizedImportMap, }); const writeEnd = performance.now(); logger.info( `${colors.green('✔')} write complete. ${colors.dim( `[${((writeEnd - writeStart) / 1000).toFixed(2)}s]`, )}`, ); } export async function startWatch(state: BuildState) { const {config, devServer, isSSR, isHMR} = state; let onFileChangeCallback: OnFileChangeCallback = () => {}; devServer.onFileChange(async ({filePath}) => { // First, do our own re-build logic const fileUrls = getUrlsForFile(filePath, config); if (!fileUrls || fileUrls.length === 0) { return; } state.allFileUrlsToProcess.push(fileUrls[0]); await flushFileQueue(state, false, { isSSR, isHMR, isResolve: true, importMap: state.optimizedImportMap, }); // Then, call the user's onFileChange callback (if one was provided) await onFileChangeCallback({filePath}); }); if (devServer.hmrEngine) { logger.info(`${colors.green(`HMR ready:`)} ws://localhost:${devServer.hmrEngine.port}`); } return { onFileChange: (callback) => (onFileChangeCallback = callback), shutdown() { return devServer.shutdown(); }, }; } export async function optimize(state: BuildState) { const {config, buildDirectoryLoc} = state; // "--optimize" mode - Optimize the build. if (config.optimize || config.plugins.some((p) => p.optimize)) { const optimizeStart = performance.now(); logger.info(colors.yellow('! optimizing build...')); await runBuiltInOptimize(config); await runPipelineOptimizeStep(buildDirectoryLoc, {config}); const optimizeEnd = performance.now(); logger.info( `${colors.green('✔')} build optimized. ${colors.dim( `[${((optimizeEnd - optimizeStart) / 1000).toFixed(2)}s]`, )}`, ); } } export async function postBuildCleanup(state: BuildState) { const {buildDirectoryLoc, config, devServer} = state; await removeEmptyFolders(buildDirectoryLoc); await runPipelineCleanupStep(config); logger.info(`${colors.underline(colors.green(colors.bold('▶ Build Complete!')))}`); await devServer.shutdown(); } ================================================ FILE: snowpack/src/commands/add-rm.ts ================================================ import {send} from 'httpie'; import {cyan, dim, underline} from 'kleur/colors'; import path from 'path'; import {logger} from '../logger'; import {CommandOptions, LockfileManifest} from '../types'; import { convertLockfileToSkypackImportMap, convertSkypackImportMapToLockfile, LOCKFILE_NAME, writeLockfile, createRemotePackageSDK, } from '../util'; import {getPackageSource} from '../sources/util'; function pkgInfoFromString(str) { const idx = str.lastIndexOf('@'); if (idx <= 0) return [str]; return [str.slice(0, idx), str.slice(idx + 1)]; } export async function addCommand(addValue: string, commandOptions: CommandOptions) { const {lockfile, config} = commandOptions; if (config.packageOptions.source === 'remote-next') { throw new Error( `[remote-next] add command has been deprecated. Manually add dependencies to the "dependencies" object in your snowpack config file.`, ); } if (config.packageOptions.source !== 'remote') { throw new Error(`add command requires packageOptions.source="remote".`); } const remotePackageSDK = createRemotePackageSDK(config); let [pkgName, pkgSemver] = pkgInfoFromString(addValue); const installMessage = pkgSemver ? `${pkgName}@${pkgSemver}` : pkgName; logger.info(`fetching ${cyan(installMessage)} from CDN...`); if (!pkgSemver || pkgSemver === 'latest') { const {data} = await send('GET', `http://registry.npmjs.org/${pkgName}/latest`); pkgSemver = `^${data.version}`; } logger.info( `adding ${cyan(underline(`${pkgName}@${pkgSemver}`))} to your project lockfile. ${dim( `(${LOCKFILE_NAME})`, )}`, ); const addedDependency = {[pkgName]: pkgSemver}; const lookupResponse = await remotePackageSDK.lookupBySpecifier(pkgName, pkgSemver); if (lookupResponse.error) { throw new Error(`There was a problem looking up ${pkgName}@${pkgSemver}`); } const newLockfile: LockfileManifest = convertSkypackImportMapToLockfile( { ...lockfile?.dependencies, ...addedDependency, }, await remotePackageSDK.generateImportMap( addedDependency, lockfile ? convertLockfileToSkypackImportMap(config.packageOptions.origin, lockfile) : undefined, ), ); await writeLockfile(path.join(config.root, LOCKFILE_NAME), newLockfile); await getPackageSource(config).prepare(); } export async function rmCommand(addValue: string, commandOptions: CommandOptions) { const {lockfile, config} = commandOptions; if (config.packageOptions.source === 'remote-next') { throw new Error( `[remote-next] rm command has been deprecated. Manually remove dependencies from the "dependencies" object in your snowpack config file.`, ); } if (config.packageOptions.source !== 'remote') { throw new Error(`rm command requires packageOptions.source="remote".`); } const remotePackageSDK = createRemotePackageSDK(config); let [pkgName] = pkgInfoFromString(addValue); logger.info(`removing ${cyan(pkgName)} from project lockfile...`); const newLockfile: LockfileManifest = convertSkypackImportMapToLockfile( lockfile?.dependencies ?? {}, await remotePackageSDK.generateImportMap( {[pkgName]: null}, lockfile ? convertLockfileToSkypackImportMap(config.packageOptions.origin, lockfile) : undefined, ), ); delete newLockfile.dependencies[pkgName]; await writeLockfile(path.join(config.root, LOCKFILE_NAME), newLockfile); await getPackageSource(config).prepare(); } ================================================ FILE: snowpack/src/commands/build.ts ================================================ import type {CommandOptions, SnowpackBuildResult} from '../types'; import {logger} from '../logger'; import { createBuildState, maybeCleanBuildDirectory, addBuildFilesFromMountpoints, buildFiles, buildDependencies, optimize, writeToDisk, postBuildCleanup, startWatch, } from '../build/process'; export async function build(commandOptions: CommandOptions): Promise { const buildState = await createBuildState(commandOptions); // Start by cleaning the directory maybeCleanBuildDirectory(buildState); await addBuildFilesFromMountpoints(buildState); await buildFiles(buildState); await buildDependencies(buildState); await writeToDisk(buildState); // "--watch" mode - Start watching the file system. if (buildState.isWatch) { return startWatch(buildState); } await optimize(buildState); await postBuildCleanup(buildState); return { onFileChange: () => { throw new Error('build().onFileChange() only supported in "watch" mode.'); }, shutdown: () => { throw new Error('build().shutdown() only supported in "watch" mode.'); }, }; } export async function command(commandOptions: CommandOptions) { try { commandOptions.config.devOptions.output = commandOptions.config.devOptions.output || (commandOptions.config.buildOptions.watch ? 'dashboard' : 'stream'); await build(commandOptions); } catch (err) { logger.error(err.message); logger.error(err.stack); process.exit(1); } if (commandOptions.config.buildOptions.watch) { // We intentionally never want to exit in watch mode! return new Promise(() => {}); } } ================================================ FILE: snowpack/src/commands/dev.ts ================================================ import {FSWatcher} from 'chokidar'; import isCompressible from 'compressible'; import {InstallTarget} from 'esinstall'; import etag from 'etag'; import {EventEmitter} from 'events'; import {createReadStream, promises as fs, statSync} from 'fs'; import {fdir} from 'fdir'; import picomatch from 'picomatch'; import type {Socket} from 'net'; import http from 'http'; import http2 from 'http2'; import * as colors from 'kleur/colors'; import mime from 'mime-types'; import os from 'os'; import path from 'path'; import {performance} from 'perf_hooks'; import slash from 'slash'; import stream from 'stream'; import url from 'url'; import zlib from 'zlib'; import {generateEnvModule, getMetaUrlPath, wrapImportProxy} from '../build/build-import-proxy'; import {FileBuilder} from '../build/file-builder'; import {getBuiltFileUrls, getMountEntryForFile, getUrlsForFile} from '../build/file-urls'; import {startHmrEngine} from '../dev/hmr'; import {logger} from '../logger'; import {getPackageSource} from '../sources/util'; import {createLoader as createServerRuntime} from '../ssr-loader'; import { CommandOptions, LoadResult, LoadUrlOptions, OnFileChangeCallback, RouteConfigObject, ServerRuntime, SnowpackConfig, SnowpackDevServer, } from '../types'; import { createInstallTarget, getCacheKey, HMR_CLIENT_CODE, HMR_OVERLAY_CODE, isFsEventsEnabled, IS_DOTFILE_REGEX, openInBrowser, } from '../util'; import {getPort, startDashboard, paintEvent} from './paint'; import {cssModuleJSON} from '../build/import-css'; import {runPipelineCleanupStep} from '../build/build-pipeline'; export class OneToManyMap { readonly keyToValue = new Map(); readonly valueToKey = new Map(); add(key: string, _value: string | string[]) { const value = Array.isArray(_value) ? _value : [_value]; this.keyToValue.set(key, value); for (const val of value) { this.valueToKey.set(val, key); } } delete(key: string) { const value = this.value(key); this.keyToValue.delete(key); if (value) { for (const val of value) { this.valueToKey.delete(val); } } } key(value: string) { return this.valueToKey.get(value); } value(key: string) { return this.keyToValue.get(key); } } interface FoundFile { loc: string; type: string; // contents: Buffer; isStatic: boolean; isResolve: boolean; } const FILE_BUILD_RESULT_ERROR = `Build Result Error: There was a problem with a file build result.`; /** * If encoding is defined, return a string. Otherwise, return a Buffer. */ function encodeResponse( response: Buffer | string, encoding: BufferEncoding | undefined | null, ): Buffer | string { if (encoding === undefined) { return response; } if (encoding) { if (typeof response === 'string') { return response; } else { return response.toString(encoding); } } if (typeof response === 'string') { return Buffer.from(response); } else { return response; } } /** * A helper class for "Not Found" errors, storing data about what file lookups were attempted. */ export class NotFoundError extends Error { constructor(url: string, lookups?: string[]) { if (!lookups) { super(`Not Found (${url})`); } else { super(`Not Found (${url}):\n${lookups.map((loc) => ' ✘ ' + loc).join('\n')}`); } } } function sendResponseFile( req: http.IncomingMessage, res: http.ServerResponse, {contents, originalFileLoc, contentType}: LoadResult, ) { const body = Buffer.from(contents); const ETag = etag(body, {weak: true}); const headers: Record = { 'Accept-Ranges': 'bytes', 'Access-Control-Allow-Origin': '*', 'Content-Type': contentType || 'application/octet-stream', ETag, Vary: 'Accept-Encoding', }; if (req.headers['if-none-match'] === ETag) { res.writeHead(304, headers); res.end(); return; } let acceptEncoding = (req.headers['accept-encoding'] as string) || ''; if ( req.headers['cache-control']?.includes('no-transform') || ['HEAD', 'OPTIONS'].includes(req.method!) || !contentType || !isCompressible(contentType) ) { acceptEncoding = ''; } // Handle gzip compression if (/\bgzip\b/.test(acceptEncoding) && stream.Readable.from) { const bodyStream = stream.Readable.from([body]); headers['Content-Encoding'] = 'gzip'; res.writeHead(200, headers); stream.pipeline(bodyStream, zlib.createGzip(), res, function onError(err) { if (err) { res.end(); logger.error(`✘ An error occurred serving ${colors.bold(req.url!)}`); logger.error(typeof err !== 'string' ? err.toString() : err); } }); return; } // Handle partial requests // TODO: This throws out a lot of hard work, and ignores any build. Improve. const {range} = req.headers; if (range) { if (!originalFileLoc) { throw new Error('Virtual files do not support partial requests'); } const {size: fileSize} = statSync(originalFileLoc); const [rangeStart, rangeEnd] = range.replace(/bytes=/, '').split('-'); const start = parseInt(rangeStart, 10); const end = rangeEnd ? parseInt(rangeEnd, 10) : fileSize - 1; const chunkSize = end - start + 1; const fileStream = createReadStream(originalFileLoc, {start, end}); res.writeHead(206, { ...headers, 'Content-Range': `bytes ${start}-${end}/${fileSize}`, 'Content-Length': chunkSize, }); fileStream.pipe(res); return; } res.writeHead(200, headers); res.write(body); res.end(); } function sendResponseError(req: http.IncomingMessage, res: http.ServerResponse, status: number) { const contentType = mime.contentType(path.extname(req.url!) || '.html'); const headers: Record = { 'Access-Control-Allow-Origin': '*', 'Accept-Ranges': 'bytes', 'Content-Type': contentType || 'application/octet-stream', Vary: 'Accept-Encoding', }; res.writeHead(status, headers); res.end(); } function handleResponseError(req, res, err: Error | NotFoundError) { if (err instanceof NotFoundError) { // Don't log favicon "Not Found" errors. Browsers automatically request a favicon.ico file // from the server, which creates annoying errors for new apps / first experiences. if (req.url !== '/favicon.ico') { logger.error(`[404] ${err.message}`); } sendResponseError(req, res, 404); return; } console.log(err); logger.error(err.toString()); logger.error(`[500] ${req.url}`, { // @ts-ignore name: err.__snowpackBuildDetails?.name, }); sendResponseError(req, res, 500); return; } function getServerRuntime( sp: SnowpackDevServer, config: SnowpackConfig, options: {invalidateOnChange?: boolean} = {}, ): ServerRuntime { const runtime = createServerRuntime({ config, load: async (url) => { const result = await sp.loadUrl(url, {isSSR: true, allowStale: false, encoding: 'utf8'}); if (!result) throw new NotFoundError(url); return result; }, }); if (options.invalidateOnChange !== false) { sp.onFileChange(({filePath}) => { const url = sp.getUrlForFile(filePath); if (url) { runtime.invalidateModule(url); } }); } return runtime; } export async function startServer( commandOptions: CommandOptions, { isDev: _isDev, isWatch: _isWatch, preparePackages: _preparePackages, }: {isDev?: boolean; isWatch?: boolean; preparePackages?: boolean} = {}, ): Promise { const {config} = commandOptions; const isDev = _isDev ?? config.mode !== 'production'; const isWatch = _isWatch ?? true; const isPreparePackages = _preparePackages ?? true; const pkgSource = getPackageSource(config); if (isPreparePackages) { await pkgSource.prepare(); logger.info(colors.bold('Ready!')); } let serverStart = performance.now(); const {port: defaultPort, hostname, open, openUrl} = config.devOptions; const messageBus = new EventEmitter(); const PACKAGE_PATH_PREFIX = path.posix.join(config.buildOptions.metaUrlPath, 'pkg/'); const PACKAGE_LINK_PATH_PREFIX = path.posix.join(config.buildOptions.metaUrlPath, 'link/'); let port: number | undefined; let warnedDeprecatedPackageImport = new Set(); if (defaultPort !== 0) { port = await getPort(defaultPort); // Reset the clock if we had to wait for the user prompt to select a new port. if (port !== defaultPort) { serverStart = performance.now(); } } // Fill in any command-specific plugin methods. for (const p of config.plugins) { p.markChanged = (fileLoc) => { knownETags.clear(); onWatchEvent(fileLoc); }; } if (isWatch && config.devOptions.output === 'dashboard' && process.stdout.isTTY) { startDashboard(messageBus, config); } else { // "stream": Log relevent events to the console. messageBus.on(paintEvent.WORKER_MSG, ({id, msg}) => { logger.info(msg.trim(), {name: id}); }); } const symlinkDirectories = new Map>(); const inMemoryBuildCache = new Map(); let fileToUrlMapping = new OneToManyMap(); const excludeGlobs = [ ...config.exclude, ...(config.mode === 'test' ? [] : config.testOptions.files), ]; const foundExcludeMatch = picomatch(excludeGlobs, {ignore: '**/node_modules/**'}); for (const [mountKey, mountEntry] of Object.entries(config.mount)) { logger.debug(`Mounting directory: '${mountKey}' as URL '${mountEntry.url}'`); const files = (await new fdir() .withFullPaths() // Note: exclude() only matches directories, and not files. However, the cost // of false positives here is minor, so do this as a quick check to possibly // skip scanning into entire folder trees. .exclude((_, dirPath) => foundExcludeMatch(dirPath)) .crawl(mountKey) .withPromise()) as string[]; for (const f of files) { fileToUrlMapping.add(f, getUrlsForFile(f, config)!); } } logger.debug(`Using in-memory cache: ${fileToUrlMapping}`); const readCredentials = async (cwd: string) => { const secure = config.devOptions.secure; let cert: Buffer; let key: Buffer; if (typeof secure === 'object') { cert = secure.cert as Buffer; key = secure.key as Buffer; } else { const certPath = path.join(cwd, 'snowpack.crt'); const keyPath = path.join(cwd, 'snowpack.key'); [cert, key] = await Promise.all([fs.readFile(certPath), fs.readFile(keyPath)]); } return { cert, key, }; }; let credentials: {cert: Buffer; key: Buffer} | undefined; if (config.devOptions.secure) { try { logger.debug(`reading credentials`); credentials = await readCredentials(config.root); } catch (e) { logger.error(`✘ No HTTPS credentials found!`); logger.info(`You can specify HTTPS credentials via either: - Including credentials in your project config under ${colors.yellow(`devOptions.secure`)}. - Including ${colors.yellow('snowpack.crt')} and ${colors.yellow( 'snowpack.key', )} files in your project's root directory. You can automatically generate credentials for your project via either: - ${colors.cyan('devcert')}: ${colors.yellow('npx devcert-cli generate localhost')} https://github.com/davewasmer/devcert-cli (no install required) - ${colors.cyan('mkcert')}: ${colors.yellow( 'mkcert -install && mkcert -key-file snowpack.key -cert-file snowpack.crt localhost', )} https://github.com/FiloSottile/mkcert (install required)`); process.exit(1); } } for (const runPlugin of config.plugins) { if (runPlugin.run) { logger.debug(`starting ${runPlugin.name} run() workers`); runPlugin .run({ isDev, // @ts-ignore: internal API only log: (msg, data) => { if (msg === 'CONSOLE_INFO') { logger.info(data.msg, {name: runPlugin.name}); } else { messageBus.emit(msg, {...data, id: runPlugin.name}); } }, }) .then(() => { logger.info('Command completed.', {name: runPlugin.name}); }) .catch((err) => { logger.error(`Command exited with error code: ${err}`, {name: runPlugin.name}); process.exit(1); }); } } function getOutputExtensionMatch() { let outputExts: string[] = []; for (const plugin of config.plugins) { if (plugin.resolve) { for (const outputExt of plugin.resolve.output) { const ext = outputExt.toLowerCase(); if (!outputExts.includes(ext)) { outputExts.push(ext); } } } } outputExts = outputExts.sort((a, b) => b.split('.').length - a.split('.').length); return (base: string): string => { const basename = base.toLowerCase(); for (const ext of outputExts) { if (basename.endsWith(ext)) return ext; } return path.extname(basename); }; } const matchOutputExt = getOutputExtensionMatch(); async function loadUrl( reqUrl: string, opt?: (LoadUrlOptions & {encoding?: undefined}) | undefined, ): Promise | undefined>; async function loadUrl( reqUrl: string, opt: LoadUrlOptions & {encoding: BufferEncoding}, ): Promise | undefined>; async function loadUrl( reqUrl: string, opt: LoadUrlOptions & {encoding: null}, ): Promise | undefined>; async function loadUrl( reqUrl: string, { isSSR: _isSSR, isHMR: _isHMR, isResolve: _isResolve, encoding: _encoding, importMap, }: LoadUrlOptions = {}, ): Promise { const isSSR = _isSSR ?? false; // // Default to HMR on, but disable HMR if SSR mode is enabled. const isHMR = _isHMR ?? (!!config.devOptions.hmr && !isSSR); const encoding = _encoding ?? null; const reqUrlHmrParam = reqUrl.includes('?mtime=') && reqUrl.split('?')[1]; let reqPath = decodeURI(url.parse(reqUrl).pathname!); if (reqPath === getMetaUrlPath('/hmr-client.js', config)) { return { contents: encodeResponse(HMR_CLIENT_CODE, encoding), imports: [], originalFileLoc: null, contentType: 'application/javascript', }; } if (reqPath === getMetaUrlPath('/hmr-error-overlay.js', config)) { return { contents: encodeResponse(HMR_OVERLAY_CODE, encoding), imports: [], originalFileLoc: null, contentType: 'application/javascript', }; } if (reqPath === getMetaUrlPath('/env.js', config)) { return { contents: encodeResponse( generateEnvModule({ mode: config.mode, isSSR, configEnv: config.env, }), encoding, ), imports: [], originalFileLoc: null, contentType: 'application/javascript', }; } // * NPM Packages: // NPM packages are served via `/_snowpack/pkg/` URLs. Behavior varies based on package source (local, remote) // but as a general rule all URLs contained within are managed by the package source loader. When this URL // prefix is hit, we load the file through the selected package source loader. if (reqPath.startsWith(PACKAGE_PATH_PREFIX)) { // Backwards-compatable redirect for legacy package URLs: If someone has created an import URL manually // (ex: /_snowpack/pkg/react.js) then we need to redirect and warn to use our new API in the future. if (reqUrl.split('.').length <= 2 && config.packageOptions.source !== 'remote') { if (!warnedDeprecatedPackageImport.has(reqUrl)) { logger.warn( `(${reqUrl}) Deprecated manual package import. Please use snowpack.getUrlForPackage() to create package URLs instead.`, ); warnedDeprecatedPackageImport.add(reqUrl); } const redirectUrl = await pkgSource.resolvePackageImport( reqUrl.replace(PACKAGE_PATH_PREFIX, '').replace(/\.js/, ''), ); reqPath = decodeURI(url.parse(redirectUrl).pathname!); } const resourcePath = reqPath.replace(/\.map$/, '').replace(/\.proxy\.js$/, ''); const webModuleUrl = resourcePath.substr(PACKAGE_PATH_PREFIX.length); let loadedModule = await pkgSource.load(webModuleUrl, {isSSR}); if (!loadedModule) { throw new NotFoundError(reqPath); } if (reqPath.endsWith('.proxy.js')) { return { imports: [], contents: await wrapImportProxy({ url: resourcePath, code: loadedModule.contents, hmr: isHMR, config: config, }), originalFileLoc: null, contentType: 'application/javascript', }; } return { imports: loadedModule.imports, contents: encodeResponse(loadedModule.contents, encoding), originalFileLoc: null, contentType: mime.lookup(reqPath) || 'application/javascript', }; } // Most of the time, resourcePath should have ".map" and ".proxy.js" extensions stripped to // match the file on disk. However, sometimes the on disk is an actual source map in a static // directory, so we can't strip that info just yet. Try the exact match first, and then strip // it later on if there is no match. let resourcePath = reqPath; let resourceType = matchOutputExt(reqPath); if (IS_DOTFILE_REGEX.test(reqPath)) resourceType = ''; let foundFile: FoundFile; // * Workspaces & Linked Packages: // The "local" package resolver supports npm packages that live in a local directory, // usually a part of your monorepo/workspace. Snowpack treats these files as source files, // with each file served individually and rebuilt instantly when changed. In the future, // these linked packages may be bundled again with a rapid bundler like esbuild. if (config.workspaceRoot && reqPath.startsWith(PACKAGE_LINK_PATH_PREFIX)) { const symlinkResourceUrl = reqPath.substr(PACKAGE_LINK_PATH_PREFIX.length); const symlinkResourceLoc = path.resolve( config.workspaceRoot as string, process.platform === 'win32' ? symlinkResourceUrl.replace(/\//g, '\\') : symlinkResourceUrl, ); const symlinkResourceDirectory = path.dirname(symlinkResourceLoc); const fileStat = await fs.stat(symlinkResourceDirectory).catch(() => null); if (!fileStat) { throw new NotFoundError(reqPath, [symlinkResourceDirectory]); } // If this is the first file served out of this linked directory // - add it to our file watcher (to enable HMR) // - add it to our file<>URL mapping for future lookups // - add a promise to our directory<>promise map, which acts as // a guard to ensure no loadUrls for this directory proceed before // proccessing of this directory is done // Each directory is scanned shallowly, so nested directories inside // of `symlinkDirectories` are okay. if (!symlinkDirectories.get(symlinkResourceDirectory)) { logger.debug( `Mounting symlink directory: '${symlinkResourceDirectory}' as URL '${path.dirname( reqPath, )}'`, ); symlinkDirectories.set(symlinkResourceDirectory, processDirectory()); watcher && watcher.add(symlinkResourceDirectory); async function processDirectory() { const shallowFiles = (await new fdir() .withFullPaths() .withMaxDepth(0) .crawl(symlinkResourceDirectory) .withPromise()) as string[]; for (const f of shallowFiles) { if (fileToUrlMapping.value(f)) { logger.warn( `Warning: mounted file is being imported as a package.\n` + `Workspace & monorepo packages work automatically and do not need to be mounted.`, ); } else { fileToUrlMapping.add( f, getBuiltFileUrls(f, config).map((u) => { const url = path.posix.join( config.buildOptions.metaUrlPath, 'link', slash(path.relative(config.workspaceRoot as string, u)), ); return url; }), ); } } } } // guard: ensure directory is properly read and files registered before proceeding await symlinkDirectories.get(symlinkResourceDirectory); let attemptedFileLoc = fileToUrlMapping.key(reqPath); if (!attemptedFileLoc) { resourcePath = reqPath.replace(/\.map$/, '').replace(/\.proxy\.js$/, ''); resourceType = path.extname(resourcePath); } attemptedFileLoc = fileToUrlMapping.key(resourcePath); if (!attemptedFileLoc) { throw new NotFoundError(reqPath); } const fileLocationExists = await fs.stat(attemptedFileLoc).catch(() => null); if (!fileLocationExists) { throw new NotFoundError(reqPath, [attemptedFileLoc]); } let foundType = path.extname(reqPath); if (!foundType && attemptedFileLoc.endsWith('.html')) foundType = '.html'; if (IS_DOTFILE_REGEX.test(reqPath)) foundType = ''; foundFile = { loc: attemptedFileLoc, type: foundType, isStatic: false, isResolve: true, }; } // * Local Files // If this is not a special URL route, then treat it as a normal file request. // Check our file<>URL mapping for the most relevant match, and continue if found. // Otherwise, return a 404. else { let attemptedFileLoc = fileToUrlMapping.key(resourcePath); if (!attemptedFileLoc) { resourcePath = reqPath.replace(/\.map$/, '').replace(/\.proxy\.js$/, ''); if (resourcePath.endsWith('/')) { resourcePath += 'index.html'; // if trailing slash, pretending like /index.html was requested makes the below much easier } resourceType = path.extname(resourcePath); } attemptedFileLoc = fileToUrlMapping.key(resourcePath) || fileToUrlMapping.key(resourcePath + '.html') || fileToUrlMapping.key(resourcePath + '/index.html'); if (!attemptedFileLoc) { // last attempt: if this is a CSS Module, try and load JSON if (resourcePath.endsWith('.module.css.json')) { const srcLoc = resourcePath.replace(/\.json$/i, ''); return { imports: [], contents: cssModuleJSON(srcLoc), originalFileLoc: srcLoc, contentType: mime.lookup('.json'), }; } throw new NotFoundError(reqPath); } const [, mountEntry] = getMountEntryForFile(attemptedFileLoc, config)!; // TODO: This data type structuring/destructuring is neccesary for now, // but we hope to add "virtual file" support soon via plugins. This would // be the interface for those response types. let foundType = path.extname(reqPath); if (!foundType && attemptedFileLoc.endsWith('.html')) foundType = '.html'; if (IS_DOTFILE_REGEX.test(reqPath)) foundType = ''; foundFile = { loc: attemptedFileLoc, type: foundType, isStatic: mountEntry.static, isResolve: mountEntry.resolve, }; } const {loc: fileLoc, type: responseType} = foundFile; // TODO: Once plugins are able to add virtual files + imports, this will no longer be needed. // - isStatic Workaround: HMR plugins need to add scripts to HTML file, even if static. const isStatic = foundFile.isStatic && responseType !== '.html'; const isResolve = _isResolve ?? true; // 1. Check the hot build cache. If it's already found, then just serve it. const cacheKey = getCacheKey(fileLoc, {isSSR, mode: config.mode}); let fileBuilder: FileBuilder | undefined = inMemoryBuildCache.get(cacheKey); if (!fileBuilder) { fileBuilder = new FileBuilder({ loc: fileLoc, isDev, isSSR, isHMR, config, hmrEngine, }); // note: for Tailwind, CSS needs to avoid caching in dev server (Tailwind needs to handle rebuilding, not Snowpack) const isTailwind = config.devOptions.tailwindConfig && (fileLoc.endsWith('.css') || fileLoc.endsWith('.pcss')); if (!isTailwind) { inMemoryBuildCache.set(cacheKey, fileBuilder); } } function handleFinalizeError(err: Error) { logger.error(FILE_BUILD_RESULT_ERROR); hmrEngine && hmrEngine.broadcastMessage({ type: 'error', title: FILE_BUILD_RESULT_ERROR, errorMessage: err.toString(), fileLoc, errorStackTrace: err.stack, }); } let finalizedResponse: string | Buffer | undefined; let resolvedImports: InstallTarget[] = []; try { if (Object.keys(fileBuilder.buildOutput).length === 0) { await fileBuilder.build(isStatic); } if (resourcePath !== reqPath && reqPath.endsWith('.proxy.js')) { finalizedResponse = await fileBuilder.getProxy(resourcePath, resourceType); // CSS Modules only: also generate JSON module mapping (not imported so must be added manually) if (reqPath.endsWith('.module.css.proxy.js') && fileBuilder.buildOutput['.json']) { resolvedImports.push(createInstallTarget(`${resourcePath}.json`)); } } else if (resourcePath !== reqPath && reqPath.endsWith('.map')) { finalizedResponse = fileBuilder.getSourceMap(resourcePath); } else { if (foundFile.isResolve) { // TODO: Warn if reqUrlHmrParam was needed here? HMR can't work if URLs aren't resolved. resolvedImports = await fileBuilder.resolveImports(isResolve, reqUrlHmrParam, importMap); } finalizedResponse = fileBuilder.getResult(resourceType); } } catch (err) { handleFinalizeError(err); throw err; } if (finalizedResponse) { return { imports: resolvedImports, contents: encodeResponse(finalizedResponse, encoding), originalFileLoc: fileLoc, contentType: mime.lookup(responseType), }; } } /** * A simple map to optimize the speed of our 304 responses. If an ETag check is * sent in the request, check if it matches the last known etag for tat file. * * Remember: This is just a nice-to-have! If we get this logic wrong, it can mean * stale files in the user's cache. Feel free to clear aggressively, as needed. */ const knownETags = new Map(); function matchRouteHandler( reqUrl: string, expectHandler: 'dest', ): RouteConfigObject['dest'] | null; function matchRouteHandler( reqUrl: string, expectHandler: 'upgrade', ): RouteConfigObject['upgrade'] | null; function matchRouteHandler( reqUrl: string, expectHandler: 'dest' | 'upgrade', ): RouteConfigObject['dest'] | RouteConfigObject['upgrade'] | null { if (reqUrl.startsWith(config.buildOptions.metaUrlPath)) { return null; } const reqPath = decodeURI(url.parse(reqUrl).pathname!); const reqExt = matchOutputExt(reqPath); const isRoute = !reqExt || reqExt.toLowerCase() === '.html'; for (const route of config.routes) { if (route.match === 'routes' && !isRoute) { continue; } if (!route[expectHandler]) { continue; } if (route._srcRegex.test(reqPath)) { return route[expectHandler]; } } return null; } /** * Fully handle the response for a given request. This is used internally for * every response that the dev server sends, but it can also be used via the * JS API to handle most boilerplate around request handling. */ async function handleRequest( req: http.IncomingMessage, res: http.ServerResponse, {handleError}: {handleError?: boolean} = {}, ) { let reqUrl = req.url!; const matchedRouteHandler = matchRouteHandler(reqUrl, 'dest'); // If a route is matched, rewrite the URL or call the route function if (matchedRouteHandler) { if (typeof matchedRouteHandler === 'string') { reqUrl = matchedRouteHandler; } else { return matchedRouteHandler(req, res); } } // Check if we can send back an optimized 304 response const quickETagCheck = req.headers['if-none-match']; const quickETagCheckUrl = reqUrl.replace(/\/$/, '/index.html'); if (quickETagCheck && quickETagCheck === knownETags.get(quickETagCheckUrl)) { logger.debug(`optimized etag! sending 304...`); res.writeHead(304, {'Access-Control-Allow-Origin': '*'}); res.end(); return; } // Backwards-compatable redirect for legacy package URLs: If someone has created an import URL manually // (ex: /_snowpack/pkg/react.js) then we need to redirect and warn to use our new API in the future. if ( reqUrl.startsWith(PACKAGE_PATH_PREFIX) && reqUrl.split('.').length <= 2 && config.packageOptions.source !== 'remote' ) { if (!warnedDeprecatedPackageImport.has(reqUrl)) { logger.warn( `(${reqUrl}) Deprecated manual package import. Please use snowpack.getUrlForPackage() to create package URLs instead.`, ); warnedDeprecatedPackageImport.add(reqUrl); } const redirectUrl = await pkgSource.resolvePackageImport( reqUrl.replace(PACKAGE_PATH_PREFIX, '').replace(/\.js/, ''), ); res.writeHead(301, {Location: redirectUrl}); res.end(); return; } // Otherwise, load the file and respond if successful. try { const result = await loadUrl(reqUrl, {allowStale: true, encoding: null}); if (!result) { throw new NotFoundError(reqUrl); } sendResponseFile(req, res, result); if (result.checkStale) { await result.checkStale(); } if (result.contents) { const tag = etag(result.contents, {weak: true}); const reqPath = decodeURI(url.parse(reqUrl).pathname!); knownETags.set(reqPath, tag); } return; } catch (err) { // Some consumers may want to handle/ignore errors themselves. if (handleError === false) { throw err; } handleResponseError(req, res, err); } } async function handleUpgrade(req: http.IncomingMessage, socket: Socket, head: Buffer) { let reqUrl = req.url!; const matchedRouteHandler = matchRouteHandler(reqUrl, 'upgrade'); if (matchedRouteHandler) { matchedRouteHandler(req, socket, head); } } type Http2RequestListener = ( request: http2.Http2ServerRequest, response: http2.Http2ServerResponse, ) => void; const createServer = (responseHandler: http.RequestListener | Http2RequestListener) => { if (credentials) { return http2.createSecureServer( {...credentials!, allowHTTP1: true}, responseHandler as Http2RequestListener, ); } return http.createServer(responseHandler as http.RequestListener); }; let server: http.Server | http2.Http2Server | undefined; if (port) { server = createServer(async (req, res) => { // Attach a request logger. res.on('finish', () => { const {method, url} = req; const {statusCode} = res; logger.debug(`[${statusCode}] ${method} ${url}`); }); // Otherwise, pass requests directly to Snowpack's request handler. handleRequest(req, res); }) .on('upgrade', (req, socket, head) => { handleUpgrade(req, socket, head); }) .on('error', (err: Error) => { logger.error(colors.red(` ✘ Failed to start server at port ${colors.bold(port!)}.`), err); server!.close(); process.exit(1); }) .listen(port); // Announce server has started const remoteIps = Object.values(os.networkInterfaces()) .reduce((every: os.NetworkInterfaceInfo[], i) => [...every, ...(i || [])], []) .filter((i) => i.family === 'IPv4' && i.internal === false) .map((i) => i.address); const protocol = config.devOptions.secure ? 'https:' : 'http:'; // Log the successful server start. const startTimeMs = Math.round(performance.now() - serverStart); logger.info(colors.green(`Server started in ${startTimeMs}ms.`)); logger.info(`${colors.green('Local:')} ${`${protocol}//${hostname}:${port}`}`); if (remoteIps.length > 0) { logger.info(`${colors.green('Network:')} ${`${protocol}//${remoteIps[0]}:${port}`}`); } } // HMR Engine const {hmrEngine, handleHmrUpdate} = config.devOptions.hmr ? startHmrEngine(inMemoryBuildCache, server, port, config) : {hmrEngine: undefined, handleHmrUpdate: undefined}; // Allow the user to hook into this callback, if they like (noop by default) let onFileChangeCallbacks: OnFileChangeCallback[] = []; let watcher: FSWatcher | undefined; // Watch src files async function onWatchEvent(fileLoc: string) { logger.info( colors.cyan('File changed: ') + path.relative(config.workspaceRoot || config.root, fileLoc), ); const updatedUrls = getUrlsForFile(fileLoc, config); if (updatedUrls) { handleHmrUpdate && handleHmrUpdate(fileLoc, updatedUrls[0]); knownETags.delete(updatedUrls[0]); knownETags.delete(updatedUrls[0] + '.proxy.js'); } inMemoryBuildCache.delete(getCacheKey(fileLoc, {isSSR: true, mode: config.mode})); inMemoryBuildCache.delete(getCacheKey(fileLoc, {isSSR: false, mode: config.mode})); await Promise.all(onFileChangeCallbacks.map((callback) => callback({filePath: fileLoc}))); for (const plugin of config.plugins) { plugin.onChange && plugin.onChange({filePath: fileLoc}); } } if (isWatch) { // Start watching the file system. // Defer "chokidar" loading to here, to reduce impact on overall startup time const chokidar = await import('chokidar'); watcher = chokidar.watch([], { ignored: config.exclude.filter((k) => k !== '**/_*.{sass,scss}'), // Sass partials ignored for builds, but not for dev changes persistent: true, ignoreInitial: true, disableGlobbing: false, useFsEvents: isFsEventsEnabled(), }); watcher.on('add', async (fileLoc) => { knownETags.clear(); await pkgSource.prepareSingleFile(fileLoc); await onWatchEvent(fileLoc); fileToUrlMapping.add(fileLoc, getUrlsForFile(fileLoc, config)!); }); watcher.on('unlink', async (fileLoc) => { knownETags.clear(); await onWatchEvent(fileLoc); fileToUrlMapping.delete(fileLoc); }); watcher.on('change', async (fileLoc) => { // TODO: If this needs to build a new dependency, report to the browser via HMR event. await pkgSource.prepareSingleFile(fileLoc); await onWatchEvent(fileLoc); }); // [hmrDelay] - Let users with noisy startups delay HMR (ex: 11ty, tsc builds) setTimeout(() => { watcher!.add(Object.keys(config.mount)); if (config.devOptions.output !== 'dashboard' || !process.stdout.isTTY) { logger.info(colors.cyan('watching for file changes... ')); } }, config.devOptions.hmrDelay); } // Open the user's browser (ignore if failed) if (server && port && open && open !== 'none') { const protocol = config.devOptions.secure ? 'https:' : 'http:'; await openInBrowser(protocol, hostname, port, open, openUrl).catch((err) => { logger.debug(`Browser open error: ${err}`); }); } const sp: SnowpackDevServer = { port: port || defaultPort, hmrEngine, rawServer: server, loadUrl, handleRequest, sendResponseFile, sendResponseError, getUrlForPackage: (pkgSpec: string) => { return pkgSource.resolvePackageImport(pkgSpec); }, getUrlForFile: (fileLoc: string) => { const result = getUrlsForFile(fileLoc, config); return result ? result[0] : null; }, onFileChange: (callback) => onFileChangeCallbacks.push(callback), getServerRuntime: (options) => getServerRuntime(sp, config, options), async shutdown() { watcher && (await watcher.close()); await runPipelineCleanupStep(config); server && server.close(); hmrEngine && (await hmrEngine.stop()); }, markChanged(fileLoc) { knownETags.clear(); onWatchEvent(fileLoc); }, }; return sp; } export async function command(commandOptions: CommandOptions) { try { // Set some CLI-focused defaults commandOptions.config.devOptions.output = commandOptions.config.devOptions.output || 'dashboard'; commandOptions.config.devOptions.open = commandOptions.config.devOptions.open || 'default'; commandOptions.config.devOptions.hmr = commandOptions.config.devOptions.hmr !== false; // Start the server await startServer(commandOptions, {isWatch: true}); } catch (err) { logger.error(err.message); logger.debug(err.stack); process.exit(1); } return new Promise(() => {}); } ================================================ FILE: snowpack/src/commands/init.ts ================================================ import {promises as fs, existsSync} from 'fs'; import {bold, dim} from 'kleur/colors'; import path from 'path'; import {logger} from '../logger'; import {CommandOptions} from '../types'; import {INIT_TEMPLATE_FILE} from '../util'; export async function command(commandOptions: CommandOptions) { const {config} = commandOptions; logger.info(`Creating new project configuration file... ${dim('(snowpack.config.js)')}`); if (!existsSync(path.join(config.root, 'package.json'))) { logger.error(`Error: create a package.json file in your project root`); process.exit(1); } const destLoc = path.join(config.root, 'snowpack.config.js'); if (existsSync(destLoc)) { logger.error(`Error: File already exists, cannot overwrite ${destLoc}`); process.exit(1); } await fs.writeFile(destLoc, INIT_TEMPLATE_FILE); logger.info(`File created! Open ${bold('snowpack.config.js')} to customize your project.`); } ================================================ FILE: snowpack/src/commands/paint.ts ================================================ import spinners from 'cli-spinners'; import detectPort from 'detect-port'; import {EventEmitter} from 'events'; import * as colors from 'kleur/colors'; import readline from 'readline'; import util from 'util'; import {logger} from '../logger'; import {SnowpackConfig} from '../types'; export const paintEvent = { BUILD_FILE: 'BUILD_FILE', LOAD_ERROR: 'LOAD_ERROR', SERVER_START: 'SERVER_START', WORKER_COMPLETE: 'WORKER_COMPLETE', WORKER_MSG: 'WORKER_MSG', WORKER_RESET: 'WORKER_RESET', }; /** * Get the actual port, based on the `defaultPort`. * If the default port was not available, then we'll prompt the user if its okay * to use the next available port. */ export async function getPort(defaultPort: number): Promise { const bestAvailablePort = await detectPort(defaultPort); if (defaultPort !== bestAvailablePort) { let useNextPort: boolean = false; if (process.stdout.isTTY) { const rl = readline.createInterface({input: process.stdin, output: process.stdout}); useNextPort = await new Promise((resolve) => { rl.question( colors.yellow( `! Port ${colors.bold(defaultPort)} not available. Run on port ${colors.bold( bestAvailablePort, )} instead? (Y/n) `, ), (answer) => { resolve(!/^no?$/i.test(answer)); }, ); }); rl.close(); } if (!useNextPort) { logger.error( `✘ Port ${colors.bold(defaultPort)} not available. Use ${colors.bold( '--port', )} to specify a different port.`, ); process.exit(1); } } return bestAvailablePort; } export function startDashboard(bus: EventEmitter, _config: SnowpackConfig) { let spinnerFrame = 0; // "dashboard": Pipe console methods to the logger, and then start the dashboard. logger.debug(`attaching console.log listeners`); console.log = (...args: [any, ...any[]]) => { logger.info(util.format(...args)); }; console.warn = (...args: [any, ...any[]]) => { logger.warn(util.format(...args)); }; console.error = (...args: [any, ...any[]]) => { logger.error(util.format(...args)); }; function paintDashboard() { let dashboardMsg = colors.cyan( `${spinners.dots.frames[spinnerFrame]} watching for file changes...`, ); const lines = dashboardMsg.split('\n').length; return {msg: dashboardMsg, lines}; } function clearDashboard(num, msg?) { // Clear Info Line while (num > 0) { process.stdout.clearLine(0); process.stdout.cursorTo(0); process.stdout.moveCursor(0, -1); num--; } if (!msg || cleanTimestamp(msg) !== lastMsg) { process.stdout.moveCursor(0, 1); } } let lastMsg: string = '\0'; let lastMsgCount = 1; function addTimestamp(msg: string): string { let counter = ''; if (cleanTimestamp(msg) === lastMsg) { lastMsgCount++; counter = colors.yellow(` (x${lastMsgCount})`); } else { lastMsgCount = 1; } return msg + counter; } function cleanTimestamp(msg: string): string { return msg.replace(/^.*\]/, ''); } bus.on(paintEvent.WORKER_MSG, ({id, msg}) => { const cleanedMsg = msg.trim(); if (!cleanedMsg) { return; } for (const individualMsg of cleanedMsg.split('\n')) { logger.info(individualMsg, {name: id}); } }); let currentDashboardHeight = 1; function onLog(msg: string) { clearDashboard(currentDashboardHeight, msg); process.stdout.write(addTimestamp(msg)); lastMsg = cleanTimestamp(msg); process.stdout.write('\n'); const result = paintDashboard(); process.stdout.write(result.msg); currentDashboardHeight = result.lines; } logger.on('debug', onLog); logger.on('info', onLog); logger.on('warn', onLog); logger.on('error', onLog); setInterval(() => { spinnerFrame = (spinnerFrame + 1) % spinners.dots.frames.length; clearDashboard(currentDashboardHeight); const result = paintDashboard(); process.stdout.write(result.msg); currentDashboardHeight = result.lines; }, 1000); logger.debug(`dashboard started`); } ================================================ FILE: snowpack/src/commands/prepare.ts ================================================ import * as colors from 'kleur/colors'; import {logger} from '../logger'; import {getPackageSource} from '../sources/util'; import {CommandOptions} from '../types'; export async function command(commandOptions: CommandOptions) { const {config, lockfile} = commandOptions; logger.info(colors.yellow('! preparing your project...')); if (config.packageOptions.source === 'remote') { if (!config.packageOptions.types) { logger.info( colors.green('✔') + ' nothing to prepare. ' + colors.dim( '(if using TypeScript, set `packageOptions.types=true` to fetch package TypeScript types ahead-of-time.)', ), ); return; } if (!lockfile) { logger.info( colors.yellow( '! no dependencies found. run "snowpack add [package]" to add a dependencies to your project.', ), ); return; } } const pkgSource = getPackageSource(config); await pkgSource.prepare(); logger.info(colors.green('✔') + ' project ready!'); } ================================================ FILE: snowpack/src/config.ts ================================================ import crypto from 'crypto'; import {all as merge} from 'deepmerge'; import projectCacheDir from 'find-cache-dir'; import {existsSync} from 'fs'; import {isPlainObject} from 'is-plain-object'; import {validate} from 'jsonschema'; import {dim} from 'kleur/colors'; import os from 'os'; import path from 'path'; import {logger} from './logger'; import {esbuildPlugin} from './plugins/plugin-esbuild'; import { CLIFlags, MountEntry, PackageOptionsLocal, PackageOptionsRemote, PluginLoadResult, RouteConfigObject, SnowpackConfig, SnowpackPlugin, SnowpackUserConfig, } from './types'; import { addLeadingSlash, addTrailingSlash, NATIVE_REQUIRE, REQUIRE_OR_IMPORT, removeTrailingSlash, isPathImport, } from './util'; import {GLOBAL_CACHE_DIR} from './sources/util'; import type {Awaited} from './util'; const CONFIG_NAME = 'snowpack'; const ALWAYS_EXCLUDE = ['**/_*.{sass,scss}', '**.d.ts']; const DEFAULT_PROJECT_CACHE_DIR = projectCacheDir({name: 'snowpack'}) || // If `projectCacheDir()` is null, no node_modules directory exists. // Use the current path (hashed) to create a cache entry in the global cache instead. // Because this is specifically for dependencies, this fallback should rarely be used. path.join(GLOBAL_CACHE_DIR, crypto.createHash('md5').update(process.cwd()).digest('hex')); // default settings const DEFAULT_ROOT = process.cwd(); const DEFAULT_CONFIG: SnowpackUserConfig = { root: DEFAULT_ROOT, plugins: [], alias: {}, env: {}, exclude: [], routes: [], dependencies: {}, devOptions: { secure: false, hostname: 'localhost', port: 8080, hmrDelay: 0, hmrPort: undefined, hmrErrorOverlay: true, }, buildOptions: { out: 'build', baseUrl: '/', metaUrlPath: '_snowpack', cacheDirPath: DEFAULT_PROJECT_CACHE_DIR, clean: true, sourcemap: false, watch: false, htmlFragments: false, ssr: false, resolveProxyImports: true, }, testOptions: { files: ['__tests__/**/*', '**/*.@(spec|test).*'], }, packageOptions: {source: 'local'}, }; export const DEFAULT_PACKAGES_LOCAL_CONFIG: PackageOptionsLocal = { source: 'local', external: [], packageLookupFields: [], knownEntrypoints: [], }; export const REMOTE_PACKAGE_ORIGIN = 'https://pkg.snowpack.dev'; const DEFAULT_PACKAGES_REMOTE_CONFIG: PackageOptionsRemote = { source: 'remote', origin: REMOTE_PACKAGE_ORIGIN, external: [], knownEntrypoints: [], cache: '.snowpack', types: false, }; const configSchema = { type: 'object', properties: { mode: {type: 'string', enum: ['test', 'development', 'production']}, extends: {type: 'string'}, exclude: {type: 'array', items: {type: 'string'}}, plugins: {type: 'array'}, env: {type: 'object'}, alias: { type: 'object', additionalProperties: {type: 'string'}, }, mount: { type: 'object', additionalProperties: { oneOf: [ {type: 'string'}, { type: ['object'], properties: { url: {type: 'string'}, static: {type: 'boolean'}, resolve: {type: 'boolean'}, dot: {type: 'boolean'}, }, }, ], }, }, devOptions: { type: 'object', properties: { secure: { oneOf: [ {type: 'boolean'}, { type: 'object', properties: { cert: {}, key: {}, }, }, ], }, port: {type: 'number'}, openUrl: {type: 'string'}, open: {type: 'string'}, output: {type: 'string', enum: ['stream', 'dashboard']}, hmr: {type: 'boolean'}, hmrDelay: {type: 'number'}, hmrPort: {type: 'number'}, hmrErrorOverlay: {type: 'boolean'}, tailwindConfig: {type: 'string'}, }, }, packageOptions: { type: 'object', properties: { dest: {type: 'string'}, external: {type: 'array', items: {type: 'string'}}, treeshake: {type: 'boolean'}, installTypes: {type: 'boolean'}, polyfillNode: {type: 'boolean'}, env: { type: 'object', additionalProperties: { oneOf: [ {id: 'EnvVarString', type: 'string'}, {id: 'EnvVarNumber', type: 'number'}, {id: 'EnvVarTrue', type: 'boolean', enum: [true]}, ], }, }, rollup: { type: 'object', properties: { context: {type: 'string'}, plugins: {type: 'array', items: {type: 'object'}}, dedupe: { type: 'array', items: {type: 'string'}, }, }, }, }, }, buildOptions: { type: ['object'], properties: { out: {type: 'string'}, baseUrl: {type: 'string'}, cacheDirPath: {type: 'string'}, clean: {type: 'boolean'}, sourcemap: { oneOf: [{type: 'boolean'}, {type: 'string', enum: ['inline']}], }, watch: {type: 'boolean'}, ssr: {type: 'boolean'}, htmlFragments: {type: 'boolean'}, jsxFactory: {type: 'string'}, jsxFragment: {type: 'string'}, jsxInject: {type: 'string'}, }, }, testOptions: { type: 'object', properties: { files: {type: 'array', items: {type: 'string'}}, }, }, experiments: { type: ['object'], properties: {}, }, optimize: { type: ['object'], properties: { preload: {type: 'boolean'}, bundle: {type: 'boolean'}, loader: {type: 'object'}, splitting: {type: 'boolean'}, treeshake: {type: 'boolean'}, manifest: {type: 'boolean'}, minify: {type: 'boolean'}, target: {type: 'string'}, }, }, proxy: { type: 'object', }, }, }; /** * Convert CLI flags to an incomplete Snowpack config representation. * We need to be careful about setting properties here if the flag value * is undefined, since the deep merge strategy would then overwrite good * defaults with 'undefined'. */ export function expandCliFlags(flags: CLIFlags): SnowpackUserConfig { const result = { packageOptions: {}, devOptions: {}, buildOptions: {}, experiments: {}, } as {packageOptions: any; devOptions: any; buildOptions: any; experiments: any; optimize?: any}; const {help, version, reload, config, ...relevantFlags} = flags; const CLI_ONLY_FLAGS = ['quiet', 'verbose']; for (const [flag, val] of Object.entries(relevantFlags)) { if (flag === '_' || flag.includes('-')) { continue; } if (configSchema.properties[flag]) { result[flag] = val; continue; } if (flag === 'source') { result.packageOptions = {source: val}; continue; } if (configSchema.properties.experiments.properties[flag]) { result.experiments[flag] = val; continue; } if (configSchema.properties.optimize.properties[flag]) { result.optimize = result.optimize || {}; result.optimize[flag] = val; continue; } if (configSchema.properties.packageOptions.properties[flag]) { result.packageOptions[flag] = val; continue; } if (configSchema.properties.devOptions.properties[flag]) { result.devOptions[flag] = val; continue; } if (configSchema.properties.buildOptions.properties[flag]) { result.buildOptions[flag] = val; continue; } if (CLI_ONLY_FLAGS.includes(flag)) { continue; } logger.error(`Unknown CLI flag: "${flag}"`); process.exit(1); } if (result.packageOptions.env) { result.packageOptions.env = result.packageOptions.env.reduce((acc, id) => { const index = id.indexOf('='); const [key, val] = index > 0 ? [id.substr(0, index), id.substr(index + 1)] : [id, true]; acc[key] = val; return acc; }, {}); } return result; } /** load and normalize plugins from config */ function loadPlugins(config: SnowpackConfig): { plugins: SnowpackPlugin[]; extensionMap: Record; } { const plugins: SnowpackPlugin[] = []; function execPluginFactory(pluginFactory: any, pluginOptions: any = {}): SnowpackPlugin { let plugin: SnowpackPlugin | null = null; plugin = pluginFactory(config, pluginOptions) as SnowpackPlugin; return plugin; } function loadPluginFromConfig( pluginLoc: string, options: any, config: SnowpackConfig, ): SnowpackPlugin { if (!path.isAbsolute(pluginLoc)) { throw new Error( `Snowpack Internal Error: plugin ${pluginLoc} should have been resolved to an absolute path.`, ); } const pluginRef = NATIVE_REQUIRE(pluginLoc, {paths: [config.root]}); let plugin: SnowpackPlugin; try { plugin = typeof pluginRef.default === 'function' ? pluginRef.default : pluginRef; if (typeof plugin !== 'function') logger.error(`plugin ${pluginLoc} must export a function.`); plugin = execPluginFactory(plugin, options) as SnowpackPlugin; } catch (err) { logger.error(err.toString()); throw err; } if (!plugin.name) { plugin.name = path.relative(process.cwd(), pluginLoc); } // Add any internal plugin methods. Placeholders are okay when individual // commands implement these differently. plugin.markChanged = (file) => { logger.debug(`clearCache(${file}) called, but function not yet hooked up.`, { name: plugin.name, }); }; // Finish up. validatePlugin(plugin); return plugin; } // 2. config.plugins config.plugins.forEach((ref) => { const pluginName = Array.isArray(ref) ? ref[0] : ref; const pluginOptions = Array.isArray(ref) ? ref[1] : {}; const plugin = loadPluginFromConfig(pluginName, pluginOptions, config); logger.debug(`loaded plugin: ${pluginName}`); plugins.push(plugin); }); // add internal JS handler plugin plugins.push(execPluginFactory(esbuildPlugin, {input: ['.mjs', '.jsx', '.ts', '.tsx']})); const extensionMap = plugins.reduce((map, {resolve}) => { if (resolve) { for (const inputExt of resolve.input) { map[inputExt] = resolve.output; } } return map; }, {} as Record); return { plugins, extensionMap, }; } function normalizeMount(config: SnowpackConfig) { const mountedDirs: Record> = config.mount || {}; const normalizedMount: Record = {}; for (const [mountDir, rawMountEntry] of Object.entries(mountedDirs)) { const mountEntry: Partial = typeof rawMountEntry === 'string' ? {url: rawMountEntry, static: false, resolve: true, dot: false} : rawMountEntry; if (!mountEntry.url) { handleConfigError( `mount[${mountDir}]: Object "${mountEntry.url}" missing required "url" option.`, ); return normalizedMount; } if (mountEntry.url[0] !== '/') { handleConfigError( `mount[${mountDir}]: Value "${mountEntry.url}" must be a URL path, and start with a "/"`, ); } normalizedMount[removeTrailingSlash(mountDir)] = { url: mountEntry.url === '/' ? '/' : removeTrailingSlash(mountEntry.url), static: mountEntry.static ?? false, resolve: mountEntry.resolve ?? true, dot: mountEntry.dot ?? false, }; } // if no mounted directories, mount the root directory to the base URL if (!Object.keys(normalizedMount).length) { normalizedMount[config.root] = { url: '/', static: false, resolve: true, dot: false, }; } return normalizedMount; } function normalizeRoutes(routes: RouteConfigObject[]): RouteConfigObject[] { return routes.map(({src, dest, upgrade, match}, i) => { // Normalize if (typeof dest === 'string') { dest = addLeadingSlash(dest); } if (!src.startsWith('^')) { src = '^' + src; } if (!src.endsWith('$')) { src = src + '$'; } // Validate try { return {src, dest, upgrade, match: match || 'all', _srcRegex: new RegExp(src)}; } catch (err) { throw new Error(`config.routes[${i}].src: invalid regular expression syntax "${src}"`); } }); } /** resolve --dest relative to cwd, etc. */ function normalizeConfig(_config: SnowpackUserConfig): SnowpackConfig { // TODO: This function is really fighting with TypeScript. Now that we have an accurate // SnowpackUserConfig type, we can have this function construct a fresh config object // from scratch instead of trying to modify the user's config object in-place. let config: SnowpackConfig = _config as any as SnowpackConfig; config.mode = config.mode || (process.env.NODE_ENV as SnowpackConfig['mode']); if (config.packageOptions.source === 'local') { config.packageOptions.rollup = config.packageOptions.rollup || {}; config.packageOptions.rollup.plugins = config.packageOptions.rollup.plugins || []; } // normalize config URL/path values config.buildOptions.out = removeTrailingSlash(config.buildOptions.out); config.buildOptions.baseUrl = addTrailingSlash(config.buildOptions.baseUrl); config.buildOptions.metaUrlPath = removeTrailingSlash( addLeadingSlash(config.buildOptions.metaUrlPath), ); config.mount = normalizeMount(config); config.routes = normalizeRoutes(config.routes); config.exclude = Array.from( new Set([ ...ALWAYS_EXCLUDE, // Always ignore the final build directory. `${config.buildOptions.out}/**`, // We want to ignore all node_modules directories. `**/node_modules/**`, // If a node_modules directory is explicity mounted, it should be treated as source. // In that case, we add the mounted directory to "picomatch.ignore" elsewhere ...config.exclude, ]), ); if (config.optimize && JSON.stringify(config.optimize) !== '{}') { config.optimize = { entrypoints: config.optimize.entrypoints ?? 'auto', preload: config.optimize.preload ?? false, bundle: config.optimize.bundle ?? false, loader: config.optimize.loader, sourcemap: config.optimize.sourcemap ?? true, splitting: config.optimize.splitting ?? false, treeshake: config.optimize.treeshake ?? true, manifest: config.optimize.manifest ?? false, target: config.optimize.target ?? 'es2020', minify: config.optimize.minify ?? false, }; } else { config.optimize = undefined; } // new pipeline const {plugins, extensionMap} = loadPlugins(config); config.plugins = plugins; config._extensionMap = extensionMap; // If any plugins defined knownEntrypoints, add them here for (const {knownEntrypoints} of config.plugins) { if (knownEntrypoints) { config.packageOptions.knownEntrypoints = config.packageOptions.knownEntrypoints.concat(knownEntrypoints); } } plugins.forEach((plugin) => { if (plugin.config) { plugin.config(config); } }); return config; } function handleConfigError(msg: string) { logger.error(msg); throw new Error(msg); } function handleValidationErrors(filepath: string, err: ConfigValidationError) { const msg = `! ${filepath}\n${err.message}`; logger.error(msg); logger.info(dim(`See https://www.snowpack.dev for more info.`)); throw new Error(msg); } function handleDeprecatedConfigError(mainMsg: string, ...msgs: string[]) { const msg = `${mainMsg}\n${msgs.join('\n')}See https://www.snowpack.dev for more info.`; logger.error(msg); throw new Error(msg); } function valdiateDeprecatedConfig(rawConfig: any) { if (rawConfig.scripts) { handleDeprecatedConfigError( '[v3.0] Legacy "scripts" config is deprecated in favor of "plugins". Safe to remove if empty.', ); } if (rawConfig.proxy) { handleDeprecatedConfigError( '[v3.0] Legacy "proxy" config is deprecated in favor of "routes". Safe to remove if empty.', ); } if (rawConfig.buildOptions?.metaDir) { handleDeprecatedConfigError( '[v3.0] "config.buildOptions.metaDir" is now "config.buildOptions.metaUrlPath".', ); } if (rawConfig.buildOptions?.webModulesUrl) { handleDeprecatedConfigError( '[v3.0] "config.buildOptions.webModulesUrl" is now always set within the "config.buildOptions.metaUrlPath" directory.', ); } if (rawConfig.buildOptions?.sourceMaps) { handleDeprecatedConfigError( '[v3.0] "config.buildOptions.sourceMaps" is now "config.buildOptions.sourcemap".', ); } if (rawConfig.installOptions) { handleDeprecatedConfigError( '[v3.0] "config.installOptions" is now "config.packageOptions". Safe to remove if empty.', ); } if (rawConfig.packageOptions?.externalPackage) { handleDeprecatedConfigError( '[v3.0] "config.installOptions.externalPackage" is now "config.packageOptions.external".', ); } if (rawConfig.packageOptions?.treeshake) { handleDeprecatedConfigError( '[v3.0] "config.installOptions.treeshake" is now "config.optimize.treeshake".', ); } if (rawConfig.install) { handleDeprecatedConfigError( '[v3.0] "config.install" is now "config.packageOptions.knownEntrypoints". Safe to remove if empty.', ); } if (rawConfig.experiments?.source) { handleDeprecatedConfigError( '[v3.0] Experiment promoted! "config.experiments.source" is now "config.packageOptions.source".', ); } if (rawConfig.packageOptions?.source === 'skypack') { handleDeprecatedConfigError( '[v3.0] Renamed! "config.experiments.source=skypack" is now "config.packageOptions.source=remote".', ); } if (rawConfig.experiments?.ssr) { handleDeprecatedConfigError( '[v3.0] Experiment promoted! "config.experiments.ssr" is now "config.buildOptions.ssr".', ); } if (rawConfig.experiments?.optimize) { handleDeprecatedConfigError( '[v3.0] Experiment promoted! "config.experiments.optimize" is now "config.optimize".', ); } if (rawConfig.experiments?.routes) { handleDeprecatedConfigError( '[v3.0] Experiment promoted! "config.experiments.routes" is now "config.routes".', ); } if (rawConfig.devOptions?.fallback) { handleDeprecatedConfigError( '[v3.0] Deprecated! "devOptions.fallback" is now replaced by "routes".\n' + 'More info: https://www.snowpack.dev/guides/routing', ); } } function validatePlugin(plugin: SnowpackPlugin) { const pluginName = plugin.name; if (plugin.resolve && !plugin.load) { handleConfigError(`[${pluginName}] "resolve" config found but "load()" method missing.`); } if (!plugin.resolve && plugin.load) { handleConfigError(`[${pluginName}] "load" method found but "resolve()" config missing.`); } if (plugin.resolve && !Array.isArray(plugin.resolve.input)) { handleConfigError( `[${pluginName}] "resolve.input" should be an array of input file extensions.`, ); } if (plugin.resolve && !Array.isArray(plugin.resolve.output)) { handleConfigError( `[${pluginName}] "resolve.output" should be an array of output file extensions.`, ); } } export function validatePluginLoadResult( plugin: SnowpackPlugin, result: PluginLoadResult | string | void | undefined | null, ) { const pluginName = plugin.name; if (!result) { return; } const isValidSingleResultType = typeof result === 'string' || Buffer.isBuffer(result); if (isValidSingleResultType && plugin.resolve!.output.length !== 1) { handleConfigError( `[plugin=${pluginName}] "load()" returned a string, but "resolve.output" contains multiple possible outputs. If multiple outputs are expected, the object return format is required.`, ); } const unexpectedOutput = typeof result === 'object' && Object.keys(result).find((fileExt) => !plugin.resolve!.output.includes(fileExt)); if (unexpectedOutput) { handleConfigError( `[plugin=${pluginName}] "load()" returned entry "${unexpectedOutput}" not found in "resolve.output": ${ plugin.resolve!.output }`, ); } } /** * Get the config base path, that all relative config values should resolve to. In order: * - The directory of the config file path, if it exists. * - The config.root value, if given. * - Otherwise, the current working directory of the process. */ function getConfigBasePath(configFileLoc: string | undefined, configRoot: string | undefined) { return ( (configFileLoc && path.dirname(configFileLoc)) || (configRoot && path.resolve(process.cwd(), configRoot)) || process.cwd() ); } function resolveRelativeConfigAlias( aliasConfig: Record, configBase: string, ): SnowpackUserConfig['alias'] { const cleanAliasConfig = {}; for (const [target, replacement] of Object.entries(aliasConfig)) { const isDirectory = target.endsWith('/'); if (isPathImport(replacement)) { cleanAliasConfig[target] = isDirectory ? addTrailingSlash(path.resolve(configBase, replacement)) : removeTrailingSlash(path.resolve(configBase, replacement)); } else { cleanAliasConfig[target] = replacement; } } return cleanAliasConfig; } function resolveRelativeConfigMount( mountConfig: Record, configBase: string, ): SnowpackUserConfig['mount'] { const cleanMountConfig = {}; for (const [target, replacement] of Object.entries(mountConfig)) { cleanMountConfig[path.resolve(configBase, target)] = replacement; } return cleanMountConfig; } function resolveRelativeConfig(config: SnowpackUserConfig, configBase: string): SnowpackUserConfig { if (config.root) { config.root = path.resolve(configBase, config.root); } if (config.workspaceRoot) { config.workspaceRoot = path.resolve(configBase, config.workspaceRoot); } if (config.buildOptions?.out) { config.buildOptions.out = path.resolve(configBase, config.buildOptions.out); } if (config.packageOptions?.source === 'remote' && config.packageOptions.cache) { config.packageOptions.cache = path.resolve(configBase, config.packageOptions.cache); } if (config.extends && /^[\.\/\\]/.test(config.extends)) { config.extends = path.resolve(configBase, config.extends); } if (config.plugins) { config.plugins = config.plugins.map((plugin) => { const name = Array.isArray(plugin) ? plugin[0] : plugin; const absName = path.isAbsolute(name) ? name : require.resolve(name, {paths: [configBase]}); if (Array.isArray(plugin)) { plugin.splice(0, 1, absName); return plugin; } return absName; }); } if (config.mount) { config.mount = resolveRelativeConfigMount(config.mount, configBase); } if (config.alias) { config.alias = resolveRelativeConfigAlias(config.alias, configBase); } return config; } class ConfigValidationError extends Error { constructor(errors: (Error | string)[]) { super(`Configuration Error:\n${errors.map((err) => ` - ${err.toString()}`).join(os.EOL)}`); } } function validateConfig(config: SnowpackConfig) { for (const mountDir of Object.keys(config.mount)) { if (!existsSync(mountDir)) { logger.warn(`config.mount[${mountDir}]: mounted directory does not exist.`); } } } export function createConfiguration(config: SnowpackUserConfig = {}): SnowpackConfig { // Validate the configuration object against our schema. Report any errors. const {errors: validationErrors} = validate(config, configSchema, { propertyName: CONFIG_NAME, allowUnknownAttributes: false, }); if (validationErrors.length > 0) { throw new ConfigValidationError(validationErrors); } // Inherit any undefined values from the default configuration. If no config argument // was passed (or no configuration file found in loadConfiguration) then this function // will effectively return a copy of the DEFAULT_CONFIG object. const mergedConfig = merge( [ DEFAULT_CONFIG, { packageOptions: config.packageOptions?.source === 'remote' ? DEFAULT_PACKAGES_REMOTE_CONFIG : DEFAULT_PACKAGES_LOCAL_CONFIG, }, config, ], { isMergeableObject: (val) => isPlainObject(val) || Array.isArray(val), }, ); // Resolve relative config values. If using loadConfiguration, all config values should // already be resolved relative to the config file path so that this should be a no-op. // But, we still need to run it in case you called this function directly. const configBase = getConfigBasePath(undefined, config.root); resolveRelativeConfig(mergedConfig, configBase); const normalizedConfig = normalizeConfig(mergedConfig); validateConfig(normalizedConfig); return normalizedConfig; } async function loadConfigurationFile( filename: string, overrides: SnowpackUserConfig = {}, ): Promise<{filepath: string | undefined; config: SnowpackUserConfig} | null> { const loc = path.resolve(overrides.root || process.cwd(), filename); if (!existsSync(loc)) return null; const config = await REQUIRE_OR_IMPORT(loc); return {filepath: loc, config}; } export async function loadConfiguration( overrides: SnowpackUserConfig = {}, configPath?: string, ): Promise { let result: Awaited> = null; // if user specified --config path, load that if (configPath) { result = await loadConfigurationFile(configPath, overrides); if (!result) { throw new Error(`Snowpack config file could not be found: ${configPath}`); } } const configs = [ 'snowpack.config.mjs', 'snowpack.config.cjs', 'snowpack.config.js', 'snowpack.config.json', ]; // If no config was found above, search for one. if (!result) { for (const potentialConfigurationFile of configs) { if (result) break; result = await loadConfigurationFile(potentialConfigurationFile, overrides); } } // Support package.json "snowpack" config if (!result) { const potentialPackageJsonConfig = await loadConfigurationFile('package.json', overrides); if (potentialPackageJsonConfig && (potentialPackageJsonConfig.config as any).snowpack) { result = { filepath: potentialPackageJsonConfig.filepath, config: (potentialPackageJsonConfig.config as any).snowpack, }; } } if (!result) { logger.warn('Hint: run "snowpack init" to create a project config file. Using defaults...'); result = {filepath: undefined, config: {}}; } const {config, filepath} = result; const configBase = getConfigBasePath(filepath, config.root); valdiateDeprecatedConfig(config); valdiateDeprecatedConfig(overrides); resolveRelativeConfig(config, configBase); let extendConfig: SnowpackUserConfig = {} as SnowpackUserConfig; if (config.extends) { const extendConfigLoc = require.resolve(config.extends, {paths: [configBase]}); const extendResult = await loadConfigurationFile(extendConfigLoc, {}); if (!extendResult) { handleConfigError(`Could not locate "extends" config at ${extendConfigLoc}`); process.exit(1); } extendConfig = extendResult.config; const extendValidation = validate(extendConfig, configSchema, { allowUnknownAttributes: false, propertyName: CONFIG_NAME, }); if (extendValidation.errors && extendValidation.errors.length > 0) { handleValidationErrors(extendConfigLoc, new ConfigValidationError(extendValidation.errors)); } valdiateDeprecatedConfig(extendConfig); resolveRelativeConfig(extendConfig, configBase); } // if valid, apply config over defaults const mergedConfig = merge([extendConfig, config, overrides], { isMergeableObject: (val) => isPlainObject(val) || Array.isArray(val), }); try { return createConfiguration(mergedConfig); } catch (err) { if (err instanceof ConfigValidationError) { handleValidationErrors(filepath!, err); } throw err; } } ================================================ FILE: snowpack/src/dev/hmr.ts ================================================ import type http from 'http'; import type http2 from 'http2'; import path from 'path'; import onProcessExit from 'signal-exit'; import {FileBuilder} from '../build/file-builder'; import {EsmHmrEngine} from '../hmr-server-engine'; import {SnowpackConfig} from '../types'; import {getCacheKey, hasExtension} from '../util'; export function startHmrEngine( inMemoryBuildCache: Map, server: http.Server | http2.Http2Server | undefined, serverPort: number | undefined, config: SnowpackConfig, ) { const {hmrDelay} = config.devOptions; const hmrPort = config.devOptions.hmrPort || serverPort; const hmrEngine = new EsmHmrEngine({server, port: hmrPort, delay: hmrDelay}); onProcessExit(() => { hmrEngine.disconnectAllClients(); }); // Live Reload + File System Watching function updateOrBubble(url: string, visited: Set) { if (visited.has(url)) { return; } const node = hmrEngine.getEntry(url); const isBubbled = visited.size > 0; if (node && node.isHmrEnabled) { hmrEngine.broadcastMessage({type: 'update', url, bubbled: isBubbled}); } visited.add(url); if (node && node.isHmrAccepted) { // Found a boundary, no bubbling needed } else if (node && node.dependents.size > 0) { node.dependents.forEach((dep) => { hmrEngine.markEntryForReplacement(node, true); updateOrBubble(dep, visited); }); } else { // We've reached the top, trigger a full page refresh hmrEngine.broadcastMessage({type: 'reload'}); } } function handleHmrUpdate(fileLoc: string, originalUrl: string) { // CSS files may be loaded directly in the client (not via JS import / .proxy.js) // so send an "update" event to live update if thats the case. if (hasExtension(originalUrl, '.css') && !hasExtension(originalUrl, '.module.css')) { hmrEngine.broadcastMessage({type: 'update', url: originalUrl, bubbled: false}); } // Append ".proxy.js" to Non-JS files to match their registered URL in the // client app. let updatedUrl = originalUrl; if (!hasExtension(updatedUrl, '.js')) { updatedUrl += '.proxy.js'; } // Check if a virtual file exists in the resource cache (ex: CSS from a // Svelte file) If it does, mark it for HMR replacement but DONT trigger a // separate HMR update event. This is because a virtual resource doesn't // actually exist on disk, so we need the main resource (the JS) to load // first. Only after that happens will the CSS exist. const virtualCssFileUrl = updatedUrl.replace(/.js$/, '.css'); const virtualNode = virtualCssFileUrl.includes(path.basename(fileLoc)) && hmrEngine.getEntry(`${virtualCssFileUrl}.proxy.js`); if (virtualNode) { hmrEngine.markEntryForReplacement(virtualNode, true); } // If the changed file exists on the page, trigger a new HMR update. if (hmrEngine.getEntry(updatedUrl)) { updateOrBubble(updatedUrl, new Set()); return; } // Otherwise, reload the page if the file exists in our hot cache (which // means that the file likely exists on the current page, but is not // supported by HMR (HTML, image, etc)). if (inMemoryBuildCache.has(getCacheKey(fileLoc, {isSSR: false, mode: config.mode}))) { hmrEngine.broadcastMessage({type: 'reload'}); return; } } return {hmrEngine, handleHmrUpdate}; } ================================================ FILE: snowpack/src/hmr-server-engine.ts ================================================ import WebSocket from 'ws'; import stripAnsi from 'strip-ansi'; import type http from 'http'; import type http2 from 'http2'; import {logger} from './logger'; interface Dependency { dependents: Set; dependencies: Set; isHmrEnabled: boolean; isHmrAccepted: boolean; needsReplacement: boolean; needsReplacementCount: number; } type HMRMessage = | {type: 'reload'} | {type: 'update'; url: string; bubbled: boolean} | { type: 'error'; title: string; errorMessage: string; fileLoc?: string; errorStackTrace?: string; }; const DEFAULT_CONNECT_DELAY = 2000; const DEFAULT_PORT = 12321; interface EsmHmrEngineOptions { server: http.Server | http2.Http2Server | undefined; port?: number | undefined; delay?: number; } export class EsmHmrEngine { clients: Set = new Set(); dependencyTree = new Map(); private delay: number = 0; private currentBatch: HMRMessage[] = []; private currentBatchTimeout: NodeJS.Timer | null = null; private cachedConnectErrors: Set = new Set(); readonly port: number = 0; private wss: WebSocket.Server; constructor(options: EsmHmrEngineOptions) { this.port = options.port || DEFAULT_PORT; const wss = (this.wss = options.server ? new WebSocket.Server({noServer: true}) : new WebSocket.Server({port: this.port})); if (options.delay) { this.delay = options.delay; } if (options.server) { options.server.on('upgrade', (req, socket, head) => { // Only handle upgrades to ESM-HMR requests, ignore others. if (req.headers['sec-websocket-protocol'] !== 'esm-hmr') { return; } wss.handleUpgrade(req, socket, head, (client) => { wss.emit('connection', client, req); }); }); } wss.on('connection', (client) => { this.connectClient(client); this.registerListener(client); if (this.cachedConnectErrors.size > 0) { this.dispatchMessage(Array.from(this.cachedConnectErrors), client); } }); wss.on('close', (client: WebSocket | undefined) => { if (client) { this.disconnectClient(client); } }); } registerListener(client: WebSocket) { client.on('message', (data) => { try { const message = JSON.parse(data.toString()); if (message.type === 'hotAccept') { const entry = this.getEntry(message.id, true) as Dependency; entry.isHmrAccepted = true; entry.isHmrEnabled = true; } } catch (error) { logger.error(error.toString()); } }); } createEntry(sourceUrl: string) { const newEntry: Dependency = { dependencies: new Set(), dependents: new Set(), needsReplacement: false, needsReplacementCount: 0, isHmrEnabled: false, isHmrAccepted: false, }; this.dependencyTree.set(sourceUrl, newEntry); return newEntry; } getEntry(sourceUrl: string, createIfNotFound = false) { const result = this.dependencyTree.get(sourceUrl); if (result) { return result; } if (createIfNotFound) { return this.createEntry(sourceUrl); } return null; } setEntry(sourceUrl: string, imports: string[], isHmrEnabled = false) { const result = this.getEntry(sourceUrl, true)!; const outdatedDependencies = new Set(result.dependencies); result.isHmrEnabled = isHmrEnabled; for (const importUrl of imports) { this.addRelationship(sourceUrl, importUrl); outdatedDependencies.delete(importUrl); } for (const importUrl of outdatedDependencies) { this.removeRelationship(sourceUrl, importUrl); } } removeRelationship(sourceUrl: string, importUrl: string) { let importResult = this.getEntry(importUrl); importResult && importResult.dependents.delete(sourceUrl); const sourceResult = this.getEntry(sourceUrl); sourceResult && sourceResult.dependencies.delete(importUrl); } addRelationship(sourceUrl: string, importUrl: string) { if (importUrl !== sourceUrl) { let importResult = this.getEntry(importUrl, true)!; importResult.dependents.add(sourceUrl); const sourceResult = this.getEntry(sourceUrl, true)!; sourceResult.dependencies.add(importUrl); } } markEntryForReplacement(entry: Dependency, state: boolean) { if (state) { entry.needsReplacementCount++; } else { entry.needsReplacementCount--; } entry.needsReplacement = !!entry.needsReplacementCount; } broadcastMessage(data: HMRMessage) { // Special "error" event handling if (data.type === 'error') { // Clean: remove any console styling before we send to the browser // NOTE(@fks): If another event ever needs this, okay to generalize. data.title = data.title && stripAnsi(data.title); data.errorMessage = data.errorMessage && stripAnsi(data.errorMessage); data.fileLoc = data.fileLoc && stripAnsi(data.fileLoc); data.errorStackTrace = data.errorStackTrace && stripAnsi(data.errorStackTrace); // Cache: Cache errors in case an HMR client connects after the error (first page load). if ( Array.from(this.cachedConnectErrors).every( (f) => JSON.stringify(f) !== JSON.stringify(data), ) ) { this.cachedConnectErrors.add(data); setTimeout(() => { this.cachedConnectErrors.delete(data); }, DEFAULT_CONNECT_DELAY); } } if (this.delay > 0) { if (this.currentBatchTimeout) { clearTimeout(this.currentBatchTimeout); } this.currentBatch.push(data); this.currentBatchTimeout = setTimeout(() => this.dispatchBatch(), this.delay || 100); } else { this.dispatchMessage([data]); } } dispatchBatch() { if (this.currentBatchTimeout) { clearTimeout(this.currentBatchTimeout); } if (this.currentBatch.length > 0) { this.dispatchMessage(this.currentBatch); this.currentBatch = []; } } /** * This is shared logic to dispatch messages to the clients. The public methods * `broadcastMessage` and `dispatchBatch` manage the delay then use this, * internally when it's time to actually send the data. */ private dispatchMessage(messageBatch: HMRMessage[], singleClient?: WebSocket) { if (messageBatch.length === 0) { return; } const clientRecipientList = singleClient ? [singleClient] : this.clients; let singleSummaryMessage = messageBatch.find((message) => message.type === 'reload') || null; clientRecipientList.forEach((client) => { if (client.readyState === WebSocket.OPEN) { if (singleSummaryMessage) { client.send(JSON.stringify(singleSummaryMessage)); } else { messageBatch.forEach((data) => { client.send(JSON.stringify(data)); }); } } else { this.disconnectClient(client); } }); } connectClient(client: WebSocket) { this.clients.add(client); } disconnectClient(client: WebSocket) { client.terminate(); this.clients.delete(client); } disconnectAllClients() { for (const client of this.clients) { this.disconnectClient(client); } } stop(): Promise { // This will disconnect clients so no need to do that ourselves. return new Promise((resolve, reject) => { this.wss.close((err) => { if (err) { reject(err); } else { resolve(void 0); } }); }); } } ================================================ FILE: snowpack/src/index.ts ================================================ import * as colors from 'kleur/colors'; import util from 'util'; import yargs from 'yargs-parser'; import {addCommand, rmCommand} from './commands/add-rm'; import {command as initCommand} from './commands/init'; import {command as prepareCommand} from './commands/prepare'; import {command as buildCommand} from './commands/build'; import {command as devCommand} from './commands/dev'; import {clearCache, getPackageSource} from './sources/util'; import {logger} from './logger'; import {loadConfiguration, expandCliFlags} from './config'; import {CLIFlags, CommandOptions, SnowpackConfig} from './types'; import {readLockfile} from './util.js'; import {getUrlsForFile} from './build/file-urls'; export * from './types'; // Stable API export {startServer, NotFoundError} from './commands/dev'; export {build} from './commands/build'; export {loadConfiguration, createConfiguration} from './config.js'; export {readLockfile as loadLockfile} from './util.js'; export {clearCache} from './sources/util'; export {logger} from './logger'; // Helper API export function getUrlForFile(fileLoc: string, config: SnowpackConfig) { const result = getUrlsForFile(fileLoc, config); return result ? result[0] : result; } export function preparePackages({config}: CommandOptions) { const pkgSource = getPackageSource(config); return pkgSource.prepare(); } // Deprecated API export function startDevServer() { throw new Error('startDevServer() was been renamed to startServer().'); } export function buildProject() { throw new Error('buildProject() was been renamed to build().'); } export function loadAndValidateConfig() { throw new Error( 'loadAndValidateConfig() has been deprecated in favor of loadConfiguration() and createConfiguration().', ); } function printHelp() { logger.info( ` ${colors.bold(`snowpack`)} - A faster build system for the modern web. Snowpack is best configured via config file. But, most configuration can also be passed via CLI flags. 📖 ${colors.dim('https://www.snowpack.dev/reference/configuration')} ${colors.bold('Commands:')} snowpack init Create a new project config file. snowpack prepare Prepare your project for development (optional). snowpack dev Develop your project locally. snowpack build Build your project for production. snowpack add [package] Add a package to your project. snowpack rm [package] Remove a package from your project. ${colors.bold('Flags:')} --config [path] Set the location of your project config file. --help Show this help message. --version Show the current version. --reload Clear the local cache (useful for troubleshooting). --cache-dir-path Specify a custom cache directory. --verbose Enable verbose log messages. --quiet Enable minimal log messages. `.trim(), ); } export async function cli(args: string[]) { // parse CLI flags const cliFlags = yargs(args, { array: ['install', 'env', 'exclude', 'external'], }) as CLIFlags; if (cliFlags.verbose) { logger.level = 'debug'; } if (cliFlags.quiet) { logger.level = 'silent'; } if (cliFlags.help) { printHelp(); process.exit(0); } if (cliFlags.version) { logger.info(require('../../package.json').version); process.exit(0); } if (cliFlags.reload) { logger.info(colors.yellow('! clearing cache...')); await clearCache(); } const cmd = cliFlags['_'][2]; logger.debug(`run command: ${cmd}`); if (!cmd) { printHelp(); process.exit(1); } // Set this early -- before config loading -- so that plugins see it. if (cmd === 'build') { process.env.NODE_ENV = process.env.NODE_ENV || 'production'; } if (cmd === 'dev') { process.env.NODE_ENV = process.env.NODE_ENV || 'development'; } const cliConfig = expandCliFlags(cliFlags); const config = await loadConfiguration(cliConfig, cliFlags.config); logger.debug(`config loaded: ${util.format(config)}`); const lockfile = await readLockfile(config.root); logger.debug(`lockfile ${lockfile ? 'loaded.' : 'not loaded'}`); const commandOptions: CommandOptions = { config, lockfile, }; if (cmd === 'add') { await addCommand(cliFlags['_'][3], commandOptions); return process.exit(0); } if (cmd === 'rm') { await rmCommand(cliFlags['_'][3], commandOptions); return process.exit(0); } if (cliFlags['_'].length > 3) { logger.error(`Unexpected multiple commands`); process.exit(1); } if (cmd === 'prepare') { await prepareCommand(commandOptions); return process.exit(0); } if (cmd === 'init') { await initCommand(commandOptions); return process.exit(0); } if (cmd === 'build') { await buildCommand(commandOptions); return process.exit(0); } if (cmd === 'dev') { await devCommand(commandOptions); return process.exit(0); } logger.error(`Unrecognized command: ${cmd}`); process.exit(1); } ================================================ FILE: snowpack/src/lexer-util.ts ================================================ export function checkIdent(code: string, pos: number, text: string): boolean { return code.slice(pos, pos + text.length) === text; } export function isEOL(code: string, pos: number): boolean { return code.charAt(pos) === '\n' || (code.charAt(pos) === '\r' && code.charAt(pos + 1) === '\n'); } /* * Adopted from https://github.com/guybedford/es-module-lexer * Licensed under the * * MIT License * ----------- * * Copyright (C) 2018-2019 Guy Bedford * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ export function isBrOrWsOrPunctuatorNotDot(ch: string): boolean { const c = ch.charCodeAt(0); return (c > 8 && c < 14) || c == 32 || c == 160 || (isPunctuator(ch) && ch != '.'); } export function isPunctuator(ch: string): boolean { const c = ch.charCodeAt(0); // 23 possible punctuator endings: !%&()*+,-./:;<=>?[]^{}|~ return ( ch == '!' || ch == '%' || ch == '&' || (c > 39 && c < 48) || (c > 57 && c < 64) || ch == '[' || ch == ']' || ch == '^' || (c > 122 && c < 127) ); } export function keywordStart(source: string, pos: number) { return pos === 0 || isBrOrWsOrPunctuatorNotDot(source.charAt(pos - 1)); } ================================================ FILE: snowpack/src/logger.ts ================================================ import * as colors from 'kleur/colors'; import {LoggerLevel, LoggerEvent, LoggerOptions} from './types'; export interface LogRecord { val: string; count: number; } const levels: Record = { debug: 20, info: 30, warn: 40, error: 50, silent: 90, }; /** Custom logger heavily-inspired by https://github.com/pinojs/pino with extra features like log retentian */ class SnowpackLogger { /** set the log level (can be changed after init) */ public level: LoggerLevel = 'info'; /** configure maximum number of logs to keep (default: 500) */ public logCount = 500; private history: {val: string; count: number}[] = []; // this is immutable; must be accessed with Logger.getHistory() private callbacks: Record void> = { debug: (message: string) => { console.log(message); }, info: (message: string) => { console.log(message); }, warn: (message: string) => { console.warn(message); }, error: (message: string) => { console.error(message); }, }; private log({ level, name, message, task, }: { level: LoggerEvent; name: string; message: string; task?: Function; }) { // test if this level is enabled or not if (levels[this.level] > levels[level]) { return; // do nothing } // format let text = message; if (level === 'warn') text = colors.yellow(text); if (level === 'error') text = colors.red(text); const time = new Date(); const log = `${colors.dim( `[${String(time.getHours() + 1).padStart(2, '0')}:${String(time.getMinutes() + 1).padStart( 2, '0', )}:${String(time.getSeconds()).padStart(2, '0')}]`, )} ${colors.dim(`[${name}]`)} ${text}`; // add to log history and remove old logs to keep memory low const lastHistoryItem = this.history[this.history.length - 1]; if (lastHistoryItem && lastHistoryItem.val === log) { lastHistoryItem.count++; } else { this.history.push({val: log, count: 1}); } while (this.history.length > this.logCount) { this.history.shift(); } // log if (typeof this.callbacks[level] === 'function') { this.callbacks[level](log); } else { throw new Error(`No logging method defined for ${level}`); } // logger takes a possibly processor-intensive task, and only // processes it when this log level is enabled task && task(this); } /** emit messages only visible when --debug is passed */ public debug(message: string, options?: LoggerOptions): void { const name = (options && options.name) || 'snowpack'; this.log({level: 'debug', name, message, task: options?.task}); } /** emit general info */ public info(message: string, options?: LoggerOptions): void { const name = (options && options.name) || 'snowpack'; this.log({level: 'info', name, message, task: options?.task}); } /** emit non-fatal warnings */ public warn(message: string, options?: LoggerOptions): void { const name = (options && options.name) || 'snowpack'; this.log({level: 'warn', name, message, task: options?.task}); } /** emit critical error messages */ public error(message: string, options?: LoggerOptions): void { const name = (options && options.name) || 'snowpack'; this.log({level: 'error', name, message, task: options?.task}); } /** get full logging history */ public getHistory(): ReadonlyArray { return this.history; } /** listen for events */ public on(event: LoggerEvent, callback: (message: string) => void) { this.callbacks[event] = callback; } } /** export one logger to rest of app */ export const logger = new SnowpackLogger(); ================================================ FILE: snowpack/src/plugins/plugin-esbuild.ts ================================================ import * as esbuild from 'esbuild'; import * as colors from 'kleur/colors'; import path from 'path'; import {promises as fs} from 'fs'; import {SnowpackPlugin, SnowpackConfig} from '../types'; import {logger} from '../logger'; const IS_PREACT = /from\s+['"]preact['"]/; function checkIsPreact(contents: string) { return IS_PREACT.test(contents); } type Loader = 'js' | 'jsx' | 'ts' | 'tsx'; function getLoader(filePath: string): {loader: Loader; isJSX: boolean} { const ext = path.extname(filePath); const loader: Loader = ext === '.mjs' ? 'js' : (ext.substr(1) as Loader); const isJSX = loader.endsWith('x'); return {loader, isJSX}; } export function esbuildPlugin(config: SnowpackConfig, {input}: {input: string[]}): SnowpackPlugin { return { name: '@snowpack/plugin-esbuild', resolve: { input, output: ['.js'], }, async load({filePath}) { let contents = await fs.readFile(filePath, 'utf8'); const {loader, isJSX} = getLoader(filePath); if (isJSX) { const jsxInject = config.buildOptions.jsxInject ? `${config.buildOptions.jsxInject}\n` : ''; contents = jsxInject + contents; } const isPreact = isJSX && checkIsPreact(contents); let jsxFactory = config.buildOptions.jsxFactory ?? (isPreact ? 'h' : undefined); let jsxFragment = config.buildOptions.jsxFragment ?? (isPreact ? 'Fragment' : undefined); const {code, map, warnings} = await esbuild.transform(contents, { loader: loader, jsxFactory, jsxFragment, sourcefile: filePath, sourcemap: config.buildOptions.sourcemap && 'inline', charset: 'utf8', sourcesContent: config.mode !== 'production', }); for (const warning of warnings) { logger.error(`${colors.bold('!')} ${filePath} ${warning.text}`); } return { '.js': { code: code || '', map, }, }; }, cleanup() {}, }; } ================================================ FILE: snowpack/src/rewrite-imports.ts ================================================ import {matchDynamicImportValue} from './scan-imports'; import {CSS_REGEX, HTML_JS_REGEX, HTML_STYLE_REGEX} from './util'; const {parse} = require('es-module-lexer'); const WEBPACK_MAGIC_COMMENT_REGEX = /\/\*[\s\S]*?\*\//g; interface RewriteInstruction { start: number; end: number; rewrite: string; } function applyRewrites(source: string, rewrites: RewriteInstruction[]) { let result = ``; let index = 0; rewrites .sort((a, b) => a.start - b.start) .forEach(({start, end, rewrite}) => { result += source.substring(index, start) + rewrite; index = end; }); result += source.substring(index); return result; } export async function scanCodeImportsExports(code: string): Promise { const [imports] = await parse(code); return imports.filter((imp: any) => { //imp.d = -2 = import.meta.url = we can skip this for now if (imp.d === -2) { return false; } // imp.d > -1 === dynamic import if (imp.d > -1) { const importStatement = code.substring(imp.s, imp.e); return !!matchDynamicImportValue(importStatement); } return true; }); } export async function transformEsmImports( _code: string, replaceImport: (specifier: string) => string | Promise, ) { const imports = await scanCodeImportsExports(_code); const collectedRewrites: RewriteInstruction[] = []; await Promise.all( imports.map(async (imp) => { let spec = _code.substring(imp.s, imp.e).replace(/(\/|\\)+$/, ''); let webpackMagicCommentMatches; if (imp.d > -1) { // Extracting comments from spec as they are stripped in `matchDynamicImportValue` webpackMagicCommentMatches = spec.match(WEBPACK_MAGIC_COMMENT_REGEX); spec = matchDynamicImportValue(spec) || ''; } let rewrittenImport = await replaceImport(spec); if (imp.d > -1) { rewrittenImport = webpackMagicCommentMatches ? `${webpackMagicCommentMatches.join(' ')} ${JSON.stringify(rewrittenImport)}` : JSON.stringify(rewrittenImport); } collectedRewrites.push({rewrite: rewrittenImport, start: imp.s, end: imp.e}); }), ); const result = applyRewrites(_code, collectedRewrites); return result; } async function transformHtmlImports( code: string, replaceImport: (specifier: string) => string | Promise, ) { const collectedRewrites: RewriteInstruction[] = []; let match; const jsImportRegex = new RegExp(HTML_JS_REGEX); while ((match = jsImportRegex.exec(code))) { const [, scriptTag, scriptCode] = match; // Only transform a script element if it contains inlined code / is not empty. if (scriptCode.trim()) { collectedRewrites.push({ rewrite: await transformEsmImports(scriptCode, replaceImport), start: match.index + scriptTag.length, end: match.index + scriptTag.length + scriptCode.length, }); } } const cssImportRegex = new RegExp(HTML_STYLE_REGEX); while ((match = cssImportRegex.exec(code))) { const [, styleTag, styleCode] = match; // Only transform a script element if it contains inlined code / is not empty. if (styleCode.trim()) { collectedRewrites.push({ rewrite: await transformCssImports(styleCode, replaceImport), start: match.index + styleTag.length, end: match.index + styleTag.length + styleCode.length, }); } } const rewrittenCode = applyRewrites(code, collectedRewrites); return rewrittenCode; } async function transformCssImports( code: string, replaceImport: (specifier: string) => string | Promise, ) { const collectedRewrites: RewriteInstruction[] = []; let match; const importRegex = new RegExp(CSS_REGEX); while ((match = importRegex.exec(code))) { const [fullMatch, spec] = match; // Only transform a script element if it contains inlined code / is not empty. collectedRewrites.push({ // CSS doesn't support proxy files, so always point to the original file rewrite: `@import "${(await replaceImport(spec)).replace('.proxy.js', '')}";`, start: match.index, end: match.index + fullMatch.length, }); } const rewrittenCode = applyRewrites(code, collectedRewrites); return rewrittenCode; } export async function transformFileImports( {type, contents}: {type: string; contents: string}, replaceImport: (specifier: string) => string | Promise, ) { if (type === '.js') { return transformEsmImports(contents, replaceImport); } if (type === '.html') { return transformHtmlImports(contents, replaceImport); } if (type === '.css') { return transformCssImports(contents, replaceImport); } throw new Error( `Incompatible filetype: cannot scan ${type} files for ESM imports. This is most likely an error within Snowpack.`, ); } export async function transformAddMissingDefaultExport(_code: string) { // We need to add a default export, just so that our re-importer doesn't break const [, allExports] = await parse(_code); if (!allExports.includes('default')) { return _code + '\n\nexport default null;'; } return _code; } ================================================ FILE: snowpack/src/scan-import-glob.ts ================================================ import {keywordStart, checkIdent, isEOL} from './lexer-util'; export interface ImportGlobStatement { start: number; end: number; glob: string; isEager: boolean; } const enum ScannerState { idle, inImport, maybeImportMeta, onImportMeta, onImportMetaGlob, inSingleQuote, inDoubleQuote, inTemplateLiteral, inCall, inSingleLineComment, inMutliLineComment, } // Specifically NOT using /g here as it is stateful! const IMPORT_META_GLOB_REGEX = /import\s*\.\s*meta\s*\.\s*glob/; export function scanImportGlob(code: string) { if (!IMPORT_META_GLOB_REGEX.test(code)) return []; let pos = -1; let start = 0; let end = 0; let state = ScannerState.idle; let importGlobs: ImportGlobStatement[] = []; let importGlob: ImportGlobStatement | null = null; let glob: string = ''; while (pos++ < code.length) { const ch = code.charAt(pos); if (isInQuote(state)) { switch (ch) { case '"': case "'": case '`': { state = ScannerState.idle; break; } default: { glob += ch; } } continue; } if (isInComment(state)) { if (state === ScannerState.inSingleLineComment && isEOL(code, pos)) { state = ScannerState.idle; } else if (state === ScannerState.inMutliLineComment && checkIdent(code, pos, '*/')) { state = ScannerState.idle; } else { continue; } } switch (ch) { case '/': { if (isInQuote(state)) continue; if (code[pos + 1] === '/') { state = ScannerState.inSingleLineComment; } else if (code[pos + 1] === '*') { state = ScannerState.inMutliLineComment; } break; } case 'i': { if (keywordStart(code, pos) && checkIdent(code, pos, 'import')) { state = ScannerState.inImport; start = pos; } break; } case '.': { if (state === ScannerState.inImport) { state = ScannerState.maybeImportMeta; } break; } case 'm': { if (state === ScannerState.maybeImportMeta && checkIdent(code, pos, 'meta')) { state = ScannerState.onImportMeta; } break; } case 'g': { if (state === ScannerState.onImportMeta && checkIdent(code, pos, 'glob')) { state = ScannerState.onImportMetaGlob; const isEager = checkIdent(code, pos, 'globEager'); importGlob = {start, isEager} as any; } break; } case '"': { state = ScannerState.inDoubleQuote; glob = ''; break; } case "'": { state = ScannerState.inSingleQuote; glob = ''; break; } case '`': { state = ScannerState.inTemplateLiteral; glob = ''; break; } case '(': { if (state === ScannerState.onImportMetaGlob) state = ScannerState.inCall; break; } case ')': { state = ScannerState.idle; end = pos + 1; if (importGlob) { Object.assign(importGlob, {glob, end}); importGlobs.push(importGlob); importGlob = null; start = 0; end = 0; } break; } } } return importGlobs as ImportGlobStatement[]; } function isInQuote(state: ScannerState): boolean { return ( state === ScannerState.inDoubleQuote || state === ScannerState.inSingleQuote || state === ScannerState.inTemplateLiteral ); } function isInComment(state: ScannerState): boolean { return state === ScannerState.inSingleLineComment || state === ScannerState.inMutliLineComment; } ================================================ FILE: snowpack/src/scan-imports.ts ================================================ import {ImportSpecifier, init as initESModuleLexer, parse} from 'es-module-lexer'; import {InstallTarget} from 'esinstall'; import glob from 'glob'; import picomatch from 'picomatch'; import {fdir} from 'fdir'; import path from 'path'; import slash from 'slash'; import stripComments from 'strip-comments'; import {logger} from './logger'; import {ScannableExt, SnowpackConfig, SnowpackSourceFile} from './types'; import { createInstallTarget, CSS_REGEX, findMatchingAliasEntry, getExtension, HTML_JS_REGEX, HTML_STYLE_REGEX, isImportOfPackage, isTruthy, readFile, SVELTE_VUE_REGEX, ASTRO_REGEX, IS_DOTFILE_REGEX, } from './util'; // [@\w] - Match a word-character or @ (valid package name) // (?!.*(:\/\/)) - Ignore if previous match was a protocol (ex: http://) const BARE_SPECIFIER_REGEX = /^[@\w](?!.*(:\/\/))/; const ESM_IMPORT_REGEX = /(? 0) { installTargets.push(...scanDepList(knownEntrypoints, config.root)); } // TODO: remove this if block; move logic inside scanImports if (scannedFiles) { installTargets.push(...(await scanImportsFromFiles(scannedFiles, config))); } else { installTargets.push(...(await scanImports(config.mode === 'test', config))); } return installTargets.filter( (dep) => !config.packageOptions.external.some((packageName) => isImportOfPackage(dep.specifier, packageName), ), ); } const scannableExts = new Set([ '.astro', '.cjs', '.css', '.html', '.interface', '.js', '.jsx', '.less', '.mjs', '.sass', '.scss', '.svelte', '.ts', '.tsx', '.vue', ]); function isFileScannable(ext: string): boolean { return scannableExts.has(ext as ScannableExt); // note: needed to keep Set() correct above, but this fn should test any string (hence "as"). } export function matchDynamicImportValue(importStatement: string) { const matched = stripComments(importStatement).match(/^\s*('([^']+)'|"([^"]+)")\s*$/m); return matched?.[2] || matched?.[3] || null; } export function getWebModuleSpecifierFromCode(code: string, imp: ImportSpecifier): string | null { // import.meta: we can ignore if (imp.d === -2) { return null; } // Static imports: easy to parse if (imp.d === -1) { return code.substring(imp.s, imp.e); } // Dynamic imports: a bit trickier to parse. Today, we only support string literals. const importStatement = code.substring(imp.s, imp.e); return matchDynamicImportValue(importStatement); } /** * parses an import specifier, looking for a web modules to install. If a web module is not detected, * null is returned. */ function parseWebModuleSpecifier(specifier: string | null): null | string { if (!specifier) { return null; } // If specifier is a "bare module specifier" (ie: package name) just return it directly if (BARE_SPECIFIER_REGEX.test(specifier)) { return specifier; } return null; } function parseImportStatement(code: string, imp: ImportSpecifier): null | InstallTarget { const webModuleSpecifier = parseWebModuleSpecifier(getWebModuleSpecifierFromCode(code, imp)); if (!webModuleSpecifier) { return null; } const importStatement = stripComments(code.substring(imp.ss, imp.se)); if (/^import\s+type/.test(importStatement)) { return null; } const isDynamicImport = imp.d > -1; const hasDefaultImport = !isDynamicImport && DEFAULT_IMPORT_REGEX.test(importStatement); const hasNamespaceImport = !isDynamicImport && importStatement.includes('*'); const namedImports = (importStatement.match(HAS_NAMED_IMPORTS_REGEX)! || [, ''])[1] .split(',') // split `import { a, b, c }` by comma .map((name) => name.replace(STRIP_AS, '').trim()) // remove “ as …” and trim .filter(isTruthy); return { specifier: webModuleSpecifier, all: isDynamicImport || (!hasDefaultImport && !hasNamespaceImport && namedImports.length === 0), default: hasDefaultImport, namespace: hasNamespaceImport, named: namedImports, }; } function cleanCodeForParsing(code: string): string { code = stripComments(code); const allMatches: string[] = []; let match; const importRegex = new RegExp(ESM_IMPORT_REGEX); while ((match = importRegex.exec(code))) { allMatches.push(match); } const dynamicImportRegex = new RegExp(ESM_DYNAMIC_IMPORT_REGEX); while ((match = dynamicImportRegex.exec(code))) { allMatches.push(match); } return allMatches.map(([full]) => full).join('\n'); } function parseJsForInstallTargets(contents: string): InstallTarget[] { let imports: ImportSpecifier[] = []; // Attempt #1: Parse the file as JavaScript. JSX and some decorator // syntax will break this. try { imports.push(...parse(contents)[0]); } catch (err) { // Attempt #2: Parse only the import statements themselves. // This lets us guarentee we aren't sending any broken syntax to our parser, // but at the expense of possible false +/- caused by our regex extractor. contents = cleanCodeForParsing(contents); imports.push(...parse(contents)[0]); } return ( imports .map((imp) => parseImportStatement(contents, imp)) .filter(isTruthy) // Babel macros are not install targets! .filter((target) => !/[./]macro(\.js)?$/.test(target.specifier)) ); } function parseCssForInstallTargets(code: string): InstallTarget[] { const installTargets: InstallTarget[] = []; let match; const importRegex = new RegExp(CSS_REGEX); while ((match = importRegex.exec(code))) { const [, spec] = match; const webModuleSpecifier = parseWebModuleSpecifier(spec); if (webModuleSpecifier) { installTargets.push(createInstallTarget(webModuleSpecifier)); } } return installTargets; } function parseFileForInstallTargets({ locOnDisk, baseExt, contents, root, }: SnowpackSourceFile): InstallTarget[] { const relativeLoc = path.relative(root, locOnDisk); try { switch (baseExt as ScannableExt) { case '.css': case '.less': case '.sass': case '.scss': { logger.debug(`Scanning ${relativeLoc} for imports as CSS`); return parseCssForInstallTargets(contents); } case '.html': case '.svelte': case '.interface': case '.vue': { logger.debug(`Scanning ${relativeLoc} for imports as HTML`); return [ ...parseCssForInstallTargets(extractCssFromHtml(contents)), ...parseJsForInstallTargets(extractJsFromHtml({contents, baseExt})), ]; } case '.astro': { logger.debug(`Scanning ${relativeLoc} for imports as Astro`); return [ ...parseCssForInstallTargets(extractCssFromHtml(contents)), ...parseJsForInstallTargets(extractJsFromAstro(contents)), ]; } case '.cjs': case '.js': case '.jsx': case '.mjs': case '.ts': case '.tsx': { logger.debug(`Scanning ${relativeLoc} for imports as JS`); return parseJsForInstallTargets(contents); } default: { logger.debug( `Skip scanning ${relativeLoc} for imports (unknown file extension ${baseExt})`, ); return []; } } } catch (err) { // Another error! No hope left, just abort. logger.error(`! ${locOnDisk}`); throw err; } } /** Extract only JS within element .filter((s) => s.trim()) .join('\n'); } /** Extract only CSS within element .filter((s) => s.trim()) .join('\n'); } function extractJsFromAstro(contents: string): string { const allMatches: string[][] = []; let match; let regex = new RegExp(ASTRO_REGEX); // No while loop because we only care about the top frontmatter if ((match = regex.exec(contents))) { allMatches.push(match); } return allMatches .map((match) => match[1]) // match[1] is the code inside the frontmatter .filter((s) => s.trim()) .join('\n'); } export function scanDepList(depList: string[], cwd: string): InstallTarget[] { return depList .map((whitelistItem) => { if (!glob.hasMagic(whitelistItem)) { return [createInstallTarget(whitelistItem, true)]; } else { const nodeModulesLoc = path.join(cwd, 'node_modules'); return scanDepList(glob.sync(whitelistItem, {cwd: nodeModulesLoc, nodir: true}), cwd); } }) .reduce((flat, item) => flat.concat(item), []); } export async function scanImports( includeTests: boolean, config: SnowpackConfig, ): Promise { await initESModuleLexer; const includeFileSets = await Promise.all( Object.entries(config.mount).map(async ([fromDisk, mountEntry]) => { const allMatchedFiles = (await new fdir() .withFullPaths() .crawl(fromDisk) .withPromise()) as string[]; if (mountEntry.dot) { return allMatchedFiles; } return allMatchedFiles.filter((f) => !IS_DOTFILE_REGEX.test(slash(f))); // TODO: use a file URL instead }), ); const includeFiles = Array.from(new Set(([] as string[]).concat.apply([], includeFileSets))); if (includeFiles.length === 0) { return []; } // Scan every matched JS file for web dependency imports const excludeGlobs = includeTests ? config.exclude : [...config.exclude, ...config.testOptions.files]; const mountedNodeModules = Object.keys(config.mount).filter((v) => v.includes('node_modules')); const foundExcludeMatch = picomatch(excludeGlobs); const loadedFiles: (SnowpackSourceFile | null)[] = await Promise.all( includeFiles.map(async (filePath: string): Promise => { // don’t waste time trying to scan files that aren’t scannable if (!isFileScannable(path.extname(filePath))) { return null; } if (foundExcludeMatch(filePath)) { const isMounted = mountedNodeModules.find((mountKey) => filePath.startsWith(mountKey)); if (!isMounted || (isMounted && foundExcludeMatch(filePath.slice(isMounted.length)))) { return null; } } return { baseExt: getExtension(filePath), root: config.root, locOnDisk: filePath, contents: await readFile(filePath), }; }), ); return scanImportsFromFiles(loadedFiles.filter(isTruthy), config); } export async function scanImportsFromFiles( loadedFiles: SnowpackSourceFile[], config: SnowpackConfig, ): Promise { await initESModuleLexer; return loadedFiles .filter((sourceFile) => !Buffer.isBuffer(sourceFile.contents)) // filter out binary files from import scanning .map((sourceFile) => parseFileForInstallTargets(sourceFile as SnowpackSourceFile)) .reduce((flat, item) => flat.concat(item), []) .filter((target) => { const aliasEntry = findMatchingAliasEntry(config, target.specifier); return !aliasEntry || aliasEntry.type === 'package'; }) .sort((impA, impB) => impA.specifier.localeCompare(impB.specifier)); } ================================================ FILE: snowpack/src/sources/local-install.ts ================================================ import {install, InstallOptions as EsinstallOptions, InstallTarget} from 'esinstall'; import url from 'url'; import util from 'util'; import {buildFile} from '../build/build-pipeline'; import {logger} from '../logger'; import {ImportMap, SnowpackBuiltFile, SnowpackConfig} from '../types'; interface InstallOptions { config: SnowpackConfig; isDev: boolean; isSSR: boolean; installOptions: EsinstallOptions; installTargets: (InstallTarget | string)[]; } interface InstallResult { importMap: ImportMap; needsSsrBuild: boolean; } export async function installPackages({ config, isDev, isSSR, installOptions, installTargets, }: InstallOptions): Promise { if (installTargets.length === 0) { return { importMap: {imports: {}} as ImportMap, needsSsrBuild: false, }; } const loggerName = installTargets.length === 1 ? `esinstall:${ typeof installTargets[0] === 'string' ? installTargets[0] : installTargets[0].specifier }` : `esinstall`; let needsSsrBuild = false; const finalResult = await install(installTargets, { cwd: config.root, alias: config.alias, logger: { debug: (...args: [any, ...any[]]) => logger.debug(util.format(...args), {name: loggerName}), log: (...args: [any, ...any[]]) => logger.info(util.format(...args), {name: loggerName}), warn: (...args: [any, ...any[]]) => logger.warn(util.format(...args), {name: loggerName}), error: (...args: [any, ...any[]]) => logger.error(util.format(...args), {name: loggerName}), }, // Important! Lots of options come in through here, // `external` is a very important one to NOT override. ...installOptions, stats: false, rollup: { plugins: [ ...(installOptions?.rollup?.plugins ?? []), { name: 'esinstall:snowpack', async load(id: string) { // SSR Packages: Some file types build differently for SSR vs. non-SSR. // This line checks for those file types. Svelte is the only known file // type for now, but you can add to this line if you encounter another. needsSsrBuild = needsSsrBuild || id.endsWith('.svelte'); const output = await buildFile(url.pathToFileURL(id), { config, isDev, isSSR, isPackage: true, isHmrEnabled: false, }); let jsResponse: SnowpackBuiltFile | undefined; for (const [outputType, outputContents] of Object.entries(output)) { if (outputContents && outputType === '.js') { jsResponse = outputContents; } } if (jsResponse && Buffer.isBuffer(jsResponse.code)) { jsResponse.code = jsResponse.code.toString(); } return jsResponse as {code: string; map?: string}; }, }, ], }, }); logger.debug('Successfully ran esinstall.'); return {importMap: finalResult.importMap, needsSsrBuild}; } ================================================ FILE: snowpack/src/sources/local.ts ================================================ import Arborist from '@npmcli/arborist'; import { InstallOptions, InstallTarget, resolveDependencyManifest as _resolveDependencyManifest, resolveEntrypoint, } from 'esinstall'; import findUp from 'find-up'; import {existsSync, promises as fs} from 'fs'; import * as colors from 'kleur/colors'; import mkdirp from 'mkdirp'; import pacote from 'pacote'; import path from 'path'; import rimraf from 'rimraf'; import slash from 'slash'; import {getBuiltFileUrls} from '../build/file-urls'; import {logger} from '../logger'; import {scanCodeImportsExports, transformFileImports} from '../rewrite-imports'; import {getInstallTargets, getWebModuleSpecifierFromCode} from '../scan-imports'; import {ImportMap, PackageOptionsLocal, PackageSource, SnowpackConfig} from '../types'; import { createInstallTarget, findMatchingAliasEntry, getExtension, isJavaScript, isPathImport, isRemoteUrl, parsePackageImportSpecifier, readFile, } from '../util'; import {GLOBAL_CACHE_DIR} from './util'; import {installPackages} from './local-install'; const CURRENT_META_FILE_CONTENTS = `.snowpack cache - Do not edit this directory! The ".snowpack" cache directory is fully managed for you by Snowpack. Manual changes that you make to the files inside could break things. Commit this directory to source control to speed up cold starts. Found an issue? You can always delete the ".snowpack" directory and Snowpack will recreate it on next run. [.meta.version=2]`; const NEVER_PEER_PACKAGES: Set = new Set([ '@babel/runtime', '@babel/runtime-corejs3', 'babel-runtime', 'dom-helpers', 'es-abstract', 'node-fetch', 'whatwg-fetch', 'tslib', '@ant-design/icons-svg', '@ant-design/icons', ]); function isPackageCJS(manifest: any): boolean { return ( // If a "module" entrypoint is defined, we'll use that. !manifest.module && // If "type":"module", assume ESM. manifest.type !== 'module' && // If export map exists, assume ESM exists somewhere within it. !manifest.exports && // If "main" exists and ends in ".mjs", assume ESM. !manifest.main?.endsWith('.mjs') ); } function getRootPackageDirectory(loc: string) { const parts = loc.split('node_modules'); if (parts.length === 1) { return undefined; } const packageParts = parts.pop()!.split(path.sep).filter(Boolean); const packageRoot = path.join(parts.join('node_modules'), 'node_modules'); if (packageParts[0].startsWith('@')) { return path.join(packageRoot, packageParts[0], packageParts[1]); } else { return path.join(packageRoot, packageParts[0]); } } /** * Return your source config, in object format. pacote, arborist, and cacahe * all support the same set of options here for configuring your npm registry. */ function getNpmConnectionOptions(source: string | object): object { if (source === 'local' || source === 'remote-next') { return {}; } else if (typeof source === 'string') { return {registry: source}; } else { return source; } } type PackageImportData = { entrypoint: string; loc: string; installDest: string; packageVersion: string; packageName: string; }; export class PackageSourceLocal implements PackageSource { config: SnowpackConfig; arb: Arborist; npmConnectionOptions: object; cacheDirectory: string; packageSourceDirectory: string; memoizedResolve: Record = {}; memoizedImportMap: Record = {}; allPackageImports: Record = {}; allSymlinkImports: Record = {}; allKnownSpecs = new Set(); allKnownProjectSpecs = new Set(); hasWorkspaceWarningFired = false; constructor(config: SnowpackConfig) { this.config = config; this.npmConnectionOptions = getNpmConnectionOptions(config.packageOptions.source); if (config.packageOptions.source === 'local') { this.cacheDirectory = config.buildOptions.cacheDirPath ? path.resolve(config.buildOptions.cacheDirPath) : GLOBAL_CACHE_DIR; this.packageSourceDirectory = config.root; this.arb = null; } else { this.cacheDirectory = path.join(config.root, '.snowpack'); this.packageSourceDirectory = path.join(config.root, '.snowpack', 'source'); this.arb = new Arborist({ ...this.npmConnectionOptions, path: this.packageSourceDirectory, packageLockOnly: true, }); } } private async setupCacheDirectory() { const {config, packageSourceDirectory, cacheDirectory} = this; const lockfileLoc = path.join(cacheDirectory, 'lock.json'); const manifestLoc = path.join(packageSourceDirectory, 'package.json'); const manifestLockLoc = path.join(packageSourceDirectory, 'package-lock.json'); await mkdirp(packageSourceDirectory); if (config.dependencies) { await fs.writeFile( path.join(packageSourceDirectory, 'package.json'), JSON.stringify( { '//': 'snowpack-mananged meta file. Do not edit this file!', dependencies: config.dependencies, }, null, 2, ), 'utf8', ); if (existsSync(lockfileLoc)) { const lockfile = await fs.readFile(lockfileLoc); await fs.writeFile(manifestLockLoc, lockfile); } else { if (existsSync(manifestLockLoc)) await fs.unlink(manifestLockLoc); } } else { if (existsSync(manifestLoc)) await fs.unlink(manifestLoc); if (existsSync(manifestLockLoc)) await fs.unlink(manifestLockLoc); } } private async setupPackageRootDirectory(installTargets: InstallTarget[]) { const {arb, config} = this; const result = await arb.loadVirtual().catch(() => null); const packageNamesNeedInstall = new Set( installTargets .map((spec) => { let [_packageName] = parsePackageImportSpecifier(spec.specifier); // handle aliases const aliasEntry = findMatchingAliasEntry(config, _packageName); if (aliasEntry && aliasEntry.type === 'package') { const {from, to} = aliasEntry; _packageName = _packageName.replace(from, to); } if (!config.dependencies[_packageName]) { return _packageName; } // Needed to make TS happy. Gets filtered out in next step. return ''; }) .filter(Boolean), ); const needsInstall = result ? [...packageNamesNeedInstall].every((name) => !result.children.get(name)) : true; if (needsInstall) { await arb.buildIdealTree({add: [...packageNamesNeedInstall]}); await arb.reify(); const savedPackageLockfileLoc = path.join(this.packageSourceDirectory, 'package-lock.json'); const savedPackageLockfile = await fs.readFile(savedPackageLockfileLoc); await fs.writeFile(path.join(this.cacheDirectory, 'lock.json'), savedPackageLockfile); } } async prepare() { const installDirectoryHashLoc = path.join(this.cacheDirectory, '.meta'); const installDirectoryHash = existsSync(installDirectoryHashLoc) ? await fs.readFile(installDirectoryHashLoc, 'utf8') : undefined; if (installDirectoryHash === CURRENT_META_FILE_CONTENTS) { logger.debug(`Install directory ".meta" file is up-to-date. Welcome back!`); } else if (installDirectoryHash) { logger.info( 'Snowpack updated! Rebuilding your dependencies for the latest version of Snowpack...', ); await this.clearCache(); } else { logger.info( `${colors.bold('Welcome to Snowpack!')} Because this is your first time running\n` + `this project, Snowpack needs to prepare your dependencies. This is a one-time step\n` + `and the results will be cached for the lifetime of your project. Please wait...`, ); } const {config} = this; // If we're managing the the packages directory, setup some basic files. if (config.packageOptions.source !== 'local') { await this.setupCacheDirectory(); } // Scan your project for imports. const installTargets = await getInstallTargets(config, config.packageOptions.knownEntrypoints); this.allKnownProjectSpecs = new Set(installTargets.map((t) => t.specifier)); const allKnownPackageNames = new Set([ ...[...this.allKnownProjectSpecs].map((spec) => parsePackageImportSpecifier(spec)[0]), ...Object.keys(config.dependencies), ]); // If we're managing the the packages directory, lookup & resolve the packages. if (config.packageOptions.source !== 'local') { await this.setupPackageRootDirectory(installTargets); await Promise.all( [...allKnownPackageNames].map((packageName) => this.installPackage(packageName)), ); } for (const spec of this.allKnownProjectSpecs) { await this.buildPackageImport(spec); } // Save some metdata. Useful for next time. await mkdirp(path.dirname(installDirectoryHashLoc)); await fs.writeFile(installDirectoryHashLoc, CURRENT_META_FILE_CONTENTS, 'utf-8'); return; } async prepareSingleFile(fileLoc: string) { const {config, allKnownProjectSpecs} = this; // get install targets (imports) for a single file. const installTargets = await getInstallTargets(config, config.packageOptions.knownEntrypoints, [ { baseExt: getExtension(fileLoc), root: config.root, locOnDisk: fileLoc, contents: await readFile(fileLoc), }, ]); // Filter out all known imports, we're only looking for new ones. const newImports = installTargets.filter((t) => !allKnownProjectSpecs.has(t.specifier)); // Build all new package imports. for (const spec of newImports) { await this.buildPackageImport(spec.specifier); allKnownProjectSpecs.add(spec.specifier); } } async load(id: string, {isSSR}: {isSSR?: boolean} = {}) { const {config, allPackageImports} = this; const packageImport = allPackageImports[id]; if (!packageImport) { return; } const {loc, entrypoint, packageName, packageVersion} = packageImport; let {installDest} = packageImport; if (isSSR && existsSync(installDest + '-ssr')) { installDest += '-ssr'; } let packageCode = await fs.readFile(loc, 'utf8'); const imports: InstallTarget[] = []; const type = path.extname(loc); if (!(type === '.js' || type === '.html' || type === '.css')) { return {contents: packageCode, imports}; } const packageImportMap = JSON.parse( await fs.readFile(path.join(installDest, 'import-map.json'), 'utf8'), ); const resolveImport = async (spec): Promise => { if (isRemoteUrl(spec)) { return spec; } if (spec.startsWith('/')) { return spec; } // These are a bit tricky: relative paths within packages always point to // relative files within the built package (ex: 'pkg/common/XXX-hash.js`). // We resolve these to a new kind of "internal" import URL that's different // from the normal, flattened URL for public imports. if (isPathImport(spec)) { const newLoc = path.resolve(path.dirname(loc), spec); const resolvedSpec = slash(path.relative(installDest, newLoc)); const publicImportEntry = Object.entries(packageImportMap.imports).find( ([, v]) => v === './' + resolvedSpec, ); // If this matches the destination of a public package import, resolve to it. if (publicImportEntry) { spec = publicImportEntry[0]; return await this.resolvePackageImport(spec, {source: entrypoint}); } // Otherwise, create a relative import ID for the internal file. const relativeImportId = path.posix.join(`${packageName}.v${packageVersion}`, resolvedSpec); this.allPackageImports[relativeImportId] = { entrypoint: path.join(installDest, 'package.json'), loc: newLoc, installDest, packageVersion, packageName, }; return path.posix.join(config.buildOptions.metaUrlPath, 'pkg', relativeImportId); } // Otherwise, resolve this specifier as an external package. return await this.resolvePackageImport(spec, {source: entrypoint}); }; packageCode = await transformFileImports({type, contents: packageCode}, async (spec) => { let resolvedImportUrl = await resolveImport(spec); const importExtName = path.posix.extname(resolvedImportUrl); const isProxyImport = importExtName && importExtName !== '.js' && importExtName !== '.mjs'; if (config.buildOptions.resolveProxyImports && isProxyImport) { resolvedImportUrl = resolvedImportUrl + '.proxy.js'; } imports.push( createInstallTarget( path.resolve( path.posix.join(config.buildOptions.metaUrlPath, 'pkg', id), resolvedImportUrl, ), ), ); return resolvedImportUrl; }); return {contents: packageCode, imports}; } async modifyBuildInstallOptions(installOptions, installTargets) { const config = this.config; if (config.packageOptions.source === 'remote') { return installOptions; } installOptions.cwd = config.root; installOptions.rollup = config.packageOptions.rollup; installOptions.sourcemap = config.buildOptions.sourcemap; installOptions.polyfillNode = config.packageOptions.polyfillNode; installOptions.packageLookupFields = config.packageOptions.packageLookupFields; installOptions.packageExportLookupFields = config.packageOptions.packageExportLookupFields; if (config.packageOptions.source === 'local') { return installOptions; } installOptions.cwd = this.packageSourceDirectory; await this.setupCacheDirectory(); await this.setupPackageRootDirectory(installTargets); const buildArb = new Arborist({ ...this.npmConnectionOptions, path: this.packageSourceDirectory, }); await buildArb.buildIdealTree(); await buildArb.reify(); return installOptions; } private async resolveArbNode(packageName: string, source: string): Promise { const {config, arb} = this; if (config.packageOptions.source === 'local') { return false; } const lookupStr = path.relative(this.packageSourceDirectory, source); const lookupParts = lookupStr.split(path.sep); let lookupNode = arb.actualTree || arb.virtualTree; let exactLookupNode = lookupNode; // Use the souce file path to travel the dependency tree, // looking for the most specific match for packageName. while (lookupNode && lookupNode.children && lookupParts.length > 0) { const part = lookupParts.shift(); if (part !== 'node_modules') { continue; } let lookupPackageName = lookupParts.shift()!; if (lookupPackageName.startsWith('@')) { lookupPackageName += '/' + lookupParts.shift()!; } lookupNode = lookupNode.children.get(lookupPackageName); if (lookupNode && lookupNode.children.has(packageName)) { exactLookupNode = lookupNode; } } // If no nested match was found, exactLookupNode is still the root node. return exactLookupNode.children.get(packageName); } private async installPackage(packageName: string, _source?: string): Promise { const {config, arb} = this; const source = _source || this.packageSourceDirectory; if (config.packageOptions.source === 'local') { return false; } const arbNode = await this.resolveArbNode(packageName, source); if (!arbNode) { await arb.buildIdealTree({add: [packageName]}); await arb.reify(); // TODO: log this to the user somehow? Tell them to add the new package to dependencies obj? const savedPackageLockfileLoc = path.join(this.packageSourceDirectory, 'package-lock.json'); const savedPackageLockfile = await fs.readFile(savedPackageLockfileLoc, 'utf-8'); await fs.writeFile(path.join(this.cacheDirectory, 'lock.json'), savedPackageLockfile); // Retry. return this.installPackage(packageName, source); } if (existsSync(arbNode.path)) { return false; } await pacote.extract(arbNode.resolved, arbNode.path, this.npmConnectionOptions); return true; } private async buildPackageImport(spec: string, _source?: string, logLine = false, depth = 0) { const {config, memoizedResolve, memoizedImportMap, allKnownSpecs, allPackageImports} = this; const source = _source || this.packageSourceDirectory; const aliasEntry = findMatchingAliasEntry(config, spec); if (aliasEntry && aliasEntry.type === 'package') { const {from, to} = aliasEntry; spec = spec.replace(from, to); } const [_packageName] = parsePackageImportSpecifier(spec); // Before doing anything, check for symlinks because symlinks shouldn't be built. try { const entrypoint = resolveEntrypoint(spec, { cwd: source, packageLookupFields: [ 'snowpack:source', ...((config.packageOptions as PackageOptionsLocal).packageLookupFields || []), ], }); const isSymlink = !entrypoint.includes(path.join('node_modules', _packageName)); if (isSymlink) { return; } } catch (err) { // that's fine, package just doesn't exist yet. Go download it. } // Check to see if this package is marked as external, in which case skip the build. if (this.isExternal(_packageName, spec)) { return; } await this.installPackage(_packageName, source); const entrypoint = resolveEntrypoint(spec, { cwd: source, packageLookupFields: [ 'snowpack:source', ...((config.packageOptions as PackageOptionsLocal).packageLookupFields || []), ], }); // if this has already been memoized, exit if (memoizedResolve[entrypoint]) return memoizedResolve[entrypoint]; let rootPackageDirectory = getRootPackageDirectory(entrypoint); if (!rootPackageDirectory) { const rootPackageManifestLoc = await findUp('package.json', {cwd: entrypoint}); if (!rootPackageManifestLoc) { throw new Error(`Error resolving import ${spec}: No parent package.json found.`); } rootPackageDirectory = path.dirname(rootPackageManifestLoc); } const packageManifestLoc = path.join(rootPackageDirectory, 'package.json'); const packageManifestStr = await fs.readFile(packageManifestLoc, 'utf8'); const packageManifest = JSON.parse(packageManifestStr); const packageName = packageManifest.name || _packageName; const packageVersion = packageManifest.version || 'unknown'; const packageUID = packageName + '@' + packageVersion; const installDest = path.join(this.cacheDirectory, 'build', packageUID); const isKnownSpec = allKnownSpecs.has(`${packageUID}:${spec}`); allKnownSpecs.add(`${packageUID}:${spec}`); // NOTE(@fks): This build step used to use a queue system, which allowed multiple // parallel builds at once. Unfortunately, these builds are compute heavy and not well // parallelized, so the queue was removed but the standalone inline function remains. const newImportMap = await (async (): Promise => { // Look up the import map of the already-installed package. // If spec already exists, then this import map is valid. const lineBullet = colors.dim(depth === 0 ? '+' : '└──'.padStart(depth * 2 + 1, ' ')); let packageFormatted = spec + colors.dim('@' + packageVersion); const existingImportMapLoc = path.join(installDest, 'import-map.json'); let existingImportMap: ImportMap | undefined = memoizedImportMap[packageName]; if (!existingImportMap) { // note: this must happen BEFORE the check on disk to prevent a race condition. // If two lookups occur at once from different sources, then we mark this as “taken” immediately and finish the lookup async memoizedImportMap[packageName] = {imports: {}}; // TODO: this may not exist; should we throw an error? try { const importMapHandle = await fs.open(existingImportMapLoc, 'r+'); if (importMapHandle) { const importMapData = await importMapHandle.readFile('utf8'); existingImportMap = importMapData ? JSON.parse(importMapData) : null; memoizedImportMap[packageName] = existingImportMap as ImportMap; await importMapHandle.close(); } } catch (err) { delete memoizedImportMap[packageName]; // if there was trouble reading this, free up memoization } } // Kick off a build, if needed. let importMap = existingImportMap; let needsBuild = !existingImportMap?.imports[spec]; if (logLine || (depth === 0 && (!importMap || needsBuild))) { logLine = true; // TODO: We need to confirm version match, not just package import match const isDedupe = depth > 0 && (isKnownSpec || this.allKnownProjectSpecs.has(spec)); logger.info(`${lineBullet} ${packageFormatted}${isDedupe ? colors.dim(` (dedupe)`) : ''}`); } if (!importMap || needsBuild) { const installTargets = [...allKnownSpecs] .filter((spec) => spec.startsWith(packageUID)) .map((spec) => spec.substr(packageUID.length + 1)); // TODO: external should be a function in esinstall const filteredExternal = (external: string) => external !== _packageName && !NEVER_PEER_PACKAGES.has(external); const dependenciesAndPeerDependencies = Object.keys( packageManifest.dependencies || {}, ).concat(Object.keys(packageManifest.peerDependencies || {})); const devDependencies = Object.keys(packageManifest.devDependencies || {}); // Packages that should be marked as externalized. Any dependency // or peerDependency that is not one of the packages we want to always bundle const externalPackages = config.packageOptions.external.concat( dependenciesAndPeerDependencies.filter(filteredExternal), ); // The same as above, but includes devDependencies. const externalPackagesFull = externalPackages.concat( devDependencies.filter(filteredExternal), ); // To improve our ESM<>CJS conversion, we need to know the status of all dependencies. // This function returns a function, which can be used to fetch package.json manifests. // - When source = "local", this happens on the local file system (/w memoization). // - When source = "remote-next", this happens via remote manifest fetching (/w pacote caching). const getMemoizedResolveDependencyManifest = async () => { const results = {}; if (config.packageOptions.source === 'local') { return (packageName: string) => { results[packageName] = results[packageName] || _resolveDependencyManifest(packageName, rootPackageDirectory!)[1]; return results[packageName]; }; } await Promise.all( externalPackages.map(async (externalPackage) => { const arbNode = await this.resolveArbNode(externalPackage, rootPackageDirectory!); results[arbNode.name] = await pacote.manifest(`${arbNode.name}@${arbNode.version}`, { ...this.npmConnectionOptions, fullMetadata: true, }); }), ); return (packageName: string) => { return results[packageName]; }; }; const resolveDependencyManifest = await getMemoizedResolveDependencyManifest(); const installOptions: InstallOptions = { dest: installDest, cwd: packageManifestLoc, // This installer is only ever run in development. In production, many packages // are installed together to take advantage of tree-shaking and package bundling. env: {NODE_ENV: this.config.mode}, treeshake: false, sourcemap: config.buildOptions.sourcemap, alias: config.alias, external: externalPackagesFull, // ESM<>CJS Compatability: If we can detect that a dependency is common.js vs. ESM, then // we can provide this hint to esinstall to improve our cross-package import support. externalEsm: (imp) => { const [packageName] = parsePackageImportSpecifier(imp); const result = resolveDependencyManifest(packageName); return !result || !isPackageCJS(result); }, }; if (config.packageOptions.source === 'local') { if (config.packageOptions.polyfillNode !== undefined) { installOptions.polyfillNode = config.packageOptions.polyfillNode; } if (config.packageOptions.packageLookupFields !== undefined) { installOptions.packageLookupFields = config.packageOptions.packageLookupFields; } if (config.packageOptions.namedExports !== undefined) { installOptions.namedExports = config.packageOptions.namedExports; } if (config.packageOptions.rollup !== undefined) { installOptions.rollup = config.packageOptions.rollup; } } const installResult = await installPackages({ config, isDev: true, isSSR: false, installTargets, installOptions, }); logger.debug(`${lineBullet} ${packageFormatted} DONE`); if (installResult.needsSsrBuild) { logger.info(`${lineBullet} ${packageFormatted} ${colors.dim(`(ssr)`)}`); await installPackages({ config, isDev: true, isSSR: true, installTargets, installOptions: { ...installOptions, dest: installDest + '-ssr', }, }); logger.debug(`${lineBullet} ${packageFormatted} (ssr) DONE`); } importMap = installResult.importMap; } const dependencyFileLoc = path.join(installDest, importMap.imports[spec]); const loadedFile = await fs.readFile(dependencyFileLoc!); if (isJavaScript(dependencyFileLoc)) { const newPackageImports = new Set(); const code = loadedFile.toString('utf8'); for (const imp of await scanCodeImportsExports(code)) { let spec = getWebModuleSpecifierFromCode(code, imp); if (spec === null) { continue; } // remove trailing slash from end of specifier (easier for Node to resolve) spec = spec.replace(/(\/|\\)+$/, ''); if (isRemoteUrl(spec)) { continue; // ignore remote files } if (isPathImport(spec)) { continue; } const [scannedPackageName] = parsePackageImportSpecifier(spec); if (scannedPackageName && memoizedImportMap[scannedPackageName]) { continue; // if we’ve already installed this, then don’t reinstall } newPackageImports.add(spec); } for (const packageImport of newPackageImports) { await this.buildPackageImport(packageImport, entrypoint, logLine, depth + 1); } } return importMap; })(); const dependencyFileLoc = path.join(installDest, newImportMap.imports[spec]); // Flatten the import map value into a resolved, public import ID. // ex: "./react.js" -> "react.v17.0.1.js" const importId = newImportMap.imports[spec] .replace(/\//g, '.') .replace(/^\.+/g, '') .replace(/\.([^\.]*?)$/, `.v${packageVersion}.$1`); allPackageImports[importId] = { entrypoint, loc: dependencyFileLoc, installDest, packageName, packageVersion, }; // Memoize the result, for faster runtime lookups. memoizedResolve[entrypoint] = importId; return memoizedResolve[entrypoint]; } async resolvePackageImport( _spec: string, options: {source?: string; importMap?: ImportMap; isRetry?: boolean} = {}, ) { const {config, memoizedResolve, allSymlinkImports} = this; const source = options.source || this.packageSourceDirectory; let spec = _spec; const aliasEntry = findMatchingAliasEntry(config, spec); if (aliasEntry && aliasEntry.type === 'package') { const {from, to} = aliasEntry; spec = spec.replace(from, to); } const [packageName] = parsePackageImportSpecifier(spec); // If this import is marked as external, do not transform the original spec if (this.isExternal(packageName, spec)) { return spec; } const entrypoint = resolveEntrypoint(spec, { cwd: source, packageLookupFields: [ 'snowpack:source', ...((config.packageOptions as PackageOptionsLocal).packageLookupFields || []), ], }); // Imports in the same project should never change once resolved. Check the memoized cache here to speed up faster repeat page loads. // NOTE(fks): This is mainly needed because `resolveEntrypoint` can be slow and blocking, which creates issues when many files // are loaded/resolved at once (ex: antd). If we can improve the performance there and make that async, this may no longer be // necessary. if (!options.importMap) { if (memoizedResolve[entrypoint]) { return path.posix.join(config.buildOptions.metaUrlPath, 'pkg', memoizedResolve[entrypoint]); } } const isSymlink = !entrypoint.includes(path.join('node_modules', packageName)); const isWithinRoot = config.workspaceRoot && entrypoint.startsWith(config.workspaceRoot); if (isSymlink && config.workspaceRoot && isWithinRoot) { const builtEntrypointUrls = getBuiltFileUrls(entrypoint, config); const builtEntrypointUrl = slash( path.relative(config.workspaceRoot, builtEntrypointUrls[0]!), ); allSymlinkImports[builtEntrypointUrl] = entrypoint; return path.posix.join(config.buildOptions.metaUrlPath, 'link', builtEntrypointUrl); } else if (isSymlink && config.workspaceRoot !== false && !this.hasWorkspaceWarningFired) { this.hasWorkspaceWarningFired = true; logger.warn( colors.bold(`${spec}: Locally linked package detected outside of project root.\n`) + `If you are working in a workspace/monorepo, set your snowpack.config.js "workspaceRoot" to your workspace\n` + `directory to take advantage of fast HMR updates for linked packages. Otherwise, this package will be\n` + `cached until its package.json "version" changes. To silence this warning, set "workspaceRoot: false".`, ); } if (options.importMap) { if (options.importMap.imports[spec]) { return path.posix.join( config.buildOptions.metaUrlPath, 'pkg', options.importMap.imports[spec], ); } throw new Error(`Unexpected: spec ${spec} not included in import map.`); } // Unscanned package imports can happen. Warn the user, and then build the import individually. logger.warn( colors.bold(`${spec}: Unscannable package import found.\n`) + `Snowpack scans source files for package imports at startup, and on every change.\n` + `But, sometimes an import gets added during the build process, invisible to our file scanner.\n` + `We'll prepare this package for you now, but should add "${spec}" to "knownEntrypoints"\n` + `in your config file so that this gets prepared with the rest of your imports during startup.`, ); // Built the new import, and then try resolving again. if (options.isRetry) { throw new Error(`Unexpected: Unscanned package import "${spec}" couldn't be built/resolved.`); } await this.buildPackageImport(_spec, options.source, true); return this.resolvePackageImport(_spec, {source: options.source, isRetry: true}); } clearCache() { return rimraf.sync(this.cacheDirectory); } getCacheFolder() { return this.cacheDirectory; } private isExternal(packageName: string, specifier: string): boolean { const {config} = this; for (const external of config.packageOptions.external) { if ( packageName === external || specifier === external || packageName.startsWith(external + '/') ) { return true; } } return false; } } ================================================ FILE: snowpack/src/sources/remote.ts ================================================ import {existsSync} from 'fs'; import * as colors from 'kleur/colors'; import path from 'path'; import rimraf from 'rimraf'; import {clearCache as clearSkypackCache, rollupPluginSkypack, SkypackSDK} from 'skypack'; import util from 'util'; import {logger} from '../logger'; import {LockfileManifest, PackageOptionsRemote, PackageSource, SnowpackConfig} from '../types'; import { convertLockfileToSkypackImportMap, createRemotePackageSDK, isJavaScript, parsePackageImportSpecifier, readLockfile, } from '../util'; const fetchedPackages = new Set(); function logFetching(origin: string, packageName: string, packageSemver: string | undefined) { if (fetchedPackages.has(packageName)) { return; } fetchedPackages.add(packageName); logger.info( `import ${colors.bold(packageName + (packageSemver ? `@${packageSemver}` : ''))} ${colors.dim( `→ ${origin}/${packageName}`, )}`, ); if (!packageSemver || packageSemver === 'latest') { logger.info(colors.yellow(`pin dependency to this version: \`snowpack add ${packageName}\``)); } } /** * Remote Package Source: A generic interface through which * Snowpack interacts with the Skypack CDN. Used to load dependencies * from the CDN during both development and optimized building. */ export class PackageSourceRemote implements PackageSource { config: SnowpackConfig; lockfile: LockfileManifest | null = null; remotePackageSDK: SkypackSDK; constructor(config: SnowpackConfig) { this.config = config; this.remotePackageSDK = createRemotePackageSDK(config); } async prepare() { this.lockfile = await readLockfile(this.config.root); const {config, lockfile} = this; // Only install types if `packageOptions.types=true`. Otherwise, no need to prepare anything. if (config.packageOptions.source === 'remote' && !config.packageOptions.types) { return; } const lockEntryList = lockfile && (Object.keys(lockfile.lock) as string[]); if (!lockfile || !lockEntryList || lockEntryList.length === 0) { return; } logger.info('checking for new TypeScript types...', {name: 'packageOptions.types'}); await rimraf.sync(path.join(this.getCacheFolder(), '.snowpack/types')); for (const lockEntry of lockEntryList) { const [packageName, semverRange] = lockEntry.split('#'); const exactVersion = lockfile.lock[lockEntry]?.substr(packageName.length + 1); await this.remotePackageSDK .installTypes( packageName, exactVersion || semverRange, path.join(this.getCacheFolder(), 'types'), ) .catch((err) => logger.debug('dts fetch error: ' + err.message)); } // Skypack resolves imports on the fly, so no import map needed. logger.info(`types updated. ${colors.dim('→ ./.snowpack/types')}`, { name: 'packageOptions.types', }); } async prepareSingleFile() { // Do nothing! Skypack loads packages on-demand. } async modifyBuildInstallOptions(installOptions) { const {config, lockfile} = this; installOptions.importMap = lockfile ? convertLockfileToSkypackImportMap( (config.packageOptions as PackageOptionsRemote).origin, lockfile, ) : undefined; installOptions.rollup = installOptions.rollup || {}; installOptions.rollup.plugins = installOptions.rollup.plugins || []; installOptions.rollup.plugins.push( rollupPluginSkypack({ sdk: this.remotePackageSDK, logger: { debug: (...args: [any, ...any[]]) => logger.debug(util.format(...args)), log: (...args: [any, ...any[]]) => logger.info(util.format(...args)), warn: (...args: [any, ...any[]]) => logger.warn(util.format(...args)), error: (...args: [any, ...any[]]) => logger.error(util.format(...args)), }, }) as Plugin, ); // config.installOptions.lockfile = lockfile || undefined; return installOptions; } async load(spec: string) { const {config, lockfile} = this; let body: Buffer; if ( spec.startsWith('-/') || spec.startsWith('pin/') || spec.startsWith('new/') || spec.startsWith('error/') ) { body = (await this.remotePackageSDK.fetch(`/${spec}`)).body; } else { const [packageName, packagePath] = parsePackageImportSpecifier(spec); if (lockfile && lockfile.dependencies[packageName]) { const lockEntry = packageName + '#' + lockfile.dependencies[packageName]; if (packagePath) { body = ( await this.remotePackageSDK.fetch('/' + lockfile.lock[lockEntry] + '/' + packagePath) ).body; } else { body = (await this.remotePackageSDK.fetch('/' + lockfile.lock[lockEntry])).body; } } else { const packageSemver = 'latest'; logFetching( (config.packageOptions as PackageOptionsRemote).origin, packageName, packageSemver, ); let lookupResponse = await this.remotePackageSDK.lookupBySpecifier(spec, packageSemver); if (!lookupResponse.error && lookupResponse.importStatus === 'NEW') { const buildResponse = await this.remotePackageSDK.buildNewPackage(spec, packageSemver); if (!buildResponse.success) { throw new Error('Package could not be built!'); } lookupResponse = await this.remotePackageSDK.lookupBySpecifier(spec, packageSemver); } if (lookupResponse.error) { throw lookupResponse.error; } // Trigger a type fetch asynchronously. We want to resolve the JS as fast as possible, and // the result of this is totally disconnected from the loading flow. if (!existsSync(path.join(this.getCacheFolder(), '.snowpack/types', packageName))) { this.remotePackageSDK .installTypes( packageName, packageSemver, path.join(this.getCacheFolder(), '.snowpack/types'), ) .catch(() => 'thats fine!'); } body = lookupResponse.body; } } const ext = path.extname(spec); if (!ext || isJavaScript(spec)) { return { contents: body .toString() .replace(/(from|import) \'\//g, `$1 '${config.buildOptions.metaUrlPath}/pkg/`) .replace(/(from|import) \"\//g, `$1 "${config.buildOptions.metaUrlPath}/pkg/`), imports: [], }; } return {contents: body, imports: []}; } // TODO: Remove need for lookup URLs async resolvePackageImport(spec: string) { return path.posix.join(this.config.buildOptions.metaUrlPath, 'pkg', spec); } static clearCache() { return clearSkypackCache(); } /** @deprecated */ clearCache() { return clearSkypackCache(); } getCacheFolder() { const {config} = this; return ( (config.packageOptions.source === 'remote' && config.packageOptions.cache) || path.join(config.root, '.snowpack') ); } } ================================================ FILE: snowpack/src/sources/util.ts ================================================ import {PackageSource, SnowpackConfig} from '../types'; import {PackageSourceLocal} from './local'; import {PackageSourceRemote} from './remote'; import path from 'path'; import rimraf from 'rimraf'; import globalCacheDir from 'cachedir'; export const GLOBAL_CACHE_DIR = globalCacheDir('snowpack'); export async function clearCache() { return Promise.all([ PackageSourceRemote.clearCache(), // NOTE(v4.0): This function is called before config has been created. // But, when `packageOptions.source="remote-next"` the ".snowpack" cache // directory lives in the config.root directory. We fake it here, // and can revisit this API (probably add config as an arg) in v4.0. rimraf.sync(path.join(process.cwd(), '.snowpack')), rimraf.sync(path.join(process.cwd(), 'node_modules', '.cache', 'snowpack')), ]); } const remoteSourceCache = new WeakMap(); const localSourceCache = new WeakMap(); export function getPackageSource(config: SnowpackConfig): PackageSource { if (config.packageOptions.source === 'remote') { if (remoteSourceCache.has(config)) { return remoteSourceCache.get(config)!; } const source = new PackageSourceRemote(config); remoteSourceCache.set(config, source); return source; } if (localSourceCache.has(config)) { return localSourceCache.get(config)!; } const source = new PackageSourceLocal(config); localSourceCache.set(config, source); return source; } ================================================ FILE: snowpack/src/ssr-loader/index.ts ================================================ import {existsSync, readFileSync} from 'fs'; import {resolve, pathToFileURL} from 'url'; import {ServerRuntime, ServerRuntimeConfig, LoadResult} from '../types'; import {sourcemap_stacktrace} from './sourcemaps'; import {transform} from './transform'; import {REQUIRE_OR_IMPORT} from '../util'; interface ModuleInstance { exports: any; css: string[]; } type ModuleInitializer = () => Promise; function moduleInit(fn: ModuleInitializer) { let promise: null | Promise = null; return function () { return promise || (promise = fn()); }; } // This function makes it possible to load modules from the snowpack server, for the sake of SSR. export function createLoader({config, load}: ServerRuntimeConfig): ServerRuntime { const cache = new Map(); const graph = new Map(); async function getModule(importer: string, imported: string, urlStack: string[]) { if (imported[0] === '/' || imported[0] === '.') { const pathname = resolve(importer, imported); if (!graph.has(pathname)) graph.set(pathname, new Set()); graph.get(pathname).add(importer); return _load(pathname, urlStack); } return moduleInit(async function () { const mod = await REQUIRE_OR_IMPORT(imported, { from: config.root || config.workspaceRoot || process.cwd(), }); return { exports: mod, css: [], }; }); } function invalidateModule(path) { // If the cache doesn't have this path, check if it's a proxy file. if (!cache.has(path) && cache.has(path + '.proxy.js')) { path = path + '.proxy.js'; } cache.delete(path); const dependents = graph.get(path); graph.delete(path); if (dependents) dependents.forEach(invalidateModule); } async function _load(url, urlStack): Promise { if (urlStack.includes(url)) { console.warn(`Circular dependency: ${urlStack.join(' -> ')} -> ${url}`); return async () => ({ exports: null, css: [], }); } if (cache.has(url)) { return cache.get(url); } const promise = (async function () { const loaded = await load(url); return moduleInit(function () { try { return initializeModule(url, loaded, urlStack.concat(url)); } catch (e) { cache.delete(url); throw e; } }); })(); cache.set(url, promise); return promise; } async function initializeModule( url: string, loaded: LoadResult, urlStack: string[], ): Promise { const {code, deps, css, names} = transform(loaded.contents); const exports = {}; const allCss = new Set(css.map((relative) => resolve(url, relative))); const fileURL = loaded.originalFileLoc ? pathToFileURL(loaded.originalFileLoc) : null; // Load dependencies but do not execute. const depsLoaded: Array> = deps.map( async (dep) => { return { name: dep.name, init: await getModule(url, dep.source, urlStack), }; }, ); // Execute dependencies *in order*. const depValues: Array<{name: string; value: any}> = []; for await (const {name, init} of depsLoaded) { const module = await init(); module.css.forEach((dep) => allCss.add(dep)); depValues.push({ name: name, value: module.exports, }); } const args = [ { name: 'global', value: global, }, { name: 'require', value: (id) => { // TODO can/should this restriction be relaxed? throw new Error( `Use import instead of require (attempted to load '${id}' from '${url}')`, ); }, }, { name: names.exports, value: exports, }, { name: names.__export, value: (name, get) => { Object.defineProperty(exports, name, {get}); }, }, { name: names.__export_all, value: (mod) => { // Copy over all of the descriptors. const descriptors = Object.getOwnPropertyDescriptors(mod); Object.defineProperties(exports, descriptors); }, }, { name: names.__import, value: (source) => getModule(url, source, urlStack) .then((fn) => fn()) .then((mod) => mod.exports), }, { name: names.__import_meta, value: {url: fileURL}, }, ...depValues, ]; const fn = new Function(...args.map((d) => d.name), `${code}\n//# sourceURL=${url}`); try { fn(...args.map((d) => d.value)); } catch (e) { e.stack = await sourcemap_stacktrace(e.stack, async (address) => { if (existsSync(address)) { // it's a filepath return readFileSync(address, 'utf-8'); } try { const {contents} = await load(address); return contents; } catch { // fail gracefully } }); throw e; } return { exports, css: Array.from(allCss), }; } return { importModule: async (url) => { const init = await _load(url, []); const mod = await init(); return mod; }, invalidateModule: (url) => { invalidateModule(url); }, }; } ================================================ FILE: snowpack/src/ssr-loader/sourcemaps.ts ================================================ import path from 'path'; import {SourceMapConsumer} from 'source-map'; function get_sourcemap_url(contents) { const reversed = contents.split('\n').reverse().join('\n'); const match = /\/[/*]#[ \t]+sourceMappingURL=([^\s'"]+?)(?:[ \t]+|$)/gm.exec(reversed); if (match) return match[1]; return undefined; } async function replace_async(str, regex, asyncFn) { const promises: Promise[] = []; str.replace(regex, (match, ...args) => { const promise = asyncFn(match, ...args); promises.push(promise); }); const data = await Promise.all(promises); return str.replace(regex, () => data.shift()); } // TODO does Snowpack compose sourcemaps, or will this only undo // the last in a series of transformations? export async function sourcemap_stacktrace(stack, load_contents) { const replace = (line) => replace_async( line, /^ {4}at (?:(.+?)\s+\()?(?:(.+?):(\d+)(?::(\d+))?)\)?/, async (input, var_name, address, line, column) => { if (!address) return input; const contents = await load_contents(address); if (!contents) return input; const sourcemap_url = get_sourcemap_url(contents); if (!sourcemap_url) return input; let dir = path.dirname(address); let sourcemap_data; if (/^data:application\/json[^,]+base64,/.test(sourcemap_url)) { const raw_data = sourcemap_url.slice(sourcemap_url.indexOf(',') + 1); try { sourcemap_data = Buffer.from(raw_data, 'base64').toString(); } catch { return input; } } else { const sourcemap_path = path.resolve(dir, sourcemap_url); const data = await load_contents(sourcemap_path); if (!data) return input; sourcemap_data = data; dir = path.dirname(sourcemap_path); } let raw_sourcemap; try { raw_sourcemap = JSON.parse(sourcemap_data); } catch { return input; } // TODO: according to typings, this code cannot work; // the constructor returns a promise that needs to be awaited const consumer = await new SourceMapConsumer(raw_sourcemap); const pos = consumer.originalPositionFor({ line: Number(line), column: Number(column), bias: SourceMapConsumer.LEAST_UPPER_BOUND, }); if (!pos.source) return input; const source_path = path.resolve(dir, pos.source); const source = `${source_path}:${pos.line || 0}:${pos.column || 0}`; if (!var_name) return ` at ${source}`; return ` at ${var_name} (${source})`; }, ); return (await Promise.all(stack.split('\n').map(replace))).join('\n'); } ================================================ FILE: snowpack/src/ssr-loader/transform.ts ================================================ import * as meriyah from 'meriyah'; import MagicString from 'magic-string'; import {analyze, extract_names} from 'periscopic'; import {walk} from 'estree-walker'; import is_reference from 'is-reference'; export function transform(data) { const code = new MagicString(data); const ast: any = meriyah.parseModule(data, { ranges: true, next: true, }); const {map, scope} = analyze(ast); const all_identifiers = new Set(); // first, get a list of all the identifiers used in the module... walk(ast, { enter(node: any, parent: any) { if (is_reference(node as any, parent as any)) { all_identifiers.add(node.name); } }, }); // ...then deconflict injected values... function deconflict(name) { while (all_identifiers.has(name)) name += '_'; return name; } const exports = deconflict('exports'); const __import = deconflict('__import'); const __import_meta = deconflict('__import_meta'); const __export = deconflict('__export'); const __export_all = deconflict('__export_all'); // ...then extract imports/exports... let uid = 0; const get_import_name = () => deconflict(`__import${uid++}`); const replacements = new Map(); const deps: any[] = []; const css: any[] = []; ast.body.forEach((node) => { if (node.type === 'ImportDeclaration') { const is_namespace = node.specifiers[0] && node.specifiers[0].type === 'ImportNamespaceSpecifier'; const default_specifier = node.specifiers.find((specifier) => !specifier.imported); const name = is_namespace ? node.specifiers[0].local.name : default_specifier ? default_specifier.local.name : get_import_name(); const source = node.source.value; if (source.endsWith('.css.proxy.js') && !source.endsWith('.module.css.proxy.js')) { // CSS proxy: only include CSS css.push(source.replace(/\.proxy\.js$/, '')); } else { if (source.endsWith('.module.css.proxy.js')) { // CSS Modules Proxy: include both CSS and JS const cssSource = source.replace(/\.proxy\.js$/, ''); css.push(cssSource); } deps.push({name, source}); if (!is_namespace) { node.specifiers.forEach((specifier) => { const prop = specifier.imported ? specifier.imported.name : 'default'; replacements.set(specifier.local.name, `${name}.${prop}`); }); } } code.remove(node.start, node.end); } if (node.type === 'ExportAllDeclaration') { const source = node.source.value; const name = get_import_name(); deps.push({name, source}); code.overwrite(node.start, node.end, `${__export_all}(${name})`); } if (node.type === 'ExportDefaultDeclaration') { code.overwrite(node.start, node.declaration.start - 1, `${exports}.default = `); } if (node.type === 'ExportNamedDeclaration') { if (node.source) { const name = get_import_name(); const source = node.source.value; deps.push({name, source}); const export_block = node.specifiers .map((specifier) => { return `${__export}('${specifier.exported.name}', () => ${name}.${specifier.local.name})`; }) .join('; '); code.overwrite(node.start, node.end, export_block); } else if (node.declaration) { // `export const foo = ...` or `export function foo() {...}` code.remove(node.start, node.declaration.start); let suffix; if (node.declaration.type === 'VariableDeclaration') { const names: string[] = []; node.declaration.declarations.forEach((declarator) => { names.push(...extract_names(declarator.id)); }); suffix = names.map((name) => ` ${__export}('${name}', () => ${name});`).join(''); } else { const {name} = node.declaration.id; suffix = ` ${__export}('${name}', () => ${name});`; } code.appendLeft(node.end, suffix); } else { if (node.specifiers.length > 0) { code.remove(node.start, node.specifiers[0].start); node.specifiers.forEach((specifier) => { code.overwrite( specifier.start, specifier.end, `${__export}('${specifier.exported.name}', () => ${specifier.local.name})`, ); }); code.remove(node.specifiers[node.specifiers.length - 1].end, node.end); } else { // export {}; code.remove(node.start, node.end); } } } }); // ...then rewrite import references if (replacements.size) { let current_scope = scope; walk(ast, { enter(node: any, parent: any) { if (map.has(node)) { current_scope = map.get(node) || current_scope; } if (node.type === 'ImportDeclaration') { this.skip(); return; } if (!is_reference(node, parent)) return; if (!replacements.has(node.name)) return; if (parent.type === 'ExportSpecifier') return; if (current_scope.find_owner(node.name) === scope) { let replacement = replacements.get(node.name); if (parent.type === 'Property' && node === parent.key && node === parent.value) { replacement = `${node.name}: ${replacement}`; } code.overwrite(node.start, node.end, replacement); } }, leave(node: any) { if (map.has(node)) { if (!current_scope.parent) { throw new Error('Unexpected: (!current_scope.parent.parent)'); } current_scope = current_scope.parent; } }, }); } // replace import.meta and import(dynamic) if (/import\s*\.\s*meta/.test(data) || /import\s*\(/.test(data)) { walk(ast.body, { enter(node: any) { if (node.type === 'MetaProperty' && node.meta.name === 'import') { code.overwrite(node.start, node.end, __import_meta); } else if (node.type === 'ImportExpression') { code.overwrite(node.start, node.start + 6, __import); } }, }); } return { code: code.toString(), deps, css, names: {exports, __import, __import_meta, __export, __export_all}, }; } ================================================ FILE: snowpack/src/types.ts ================================================ import type {InstallOptions as EsinstallOptions, InstallTarget} from 'esinstall'; import type {Loader} from 'esbuild'; import type * as net from 'net'; import type * as http from 'http'; import type * as http2 from 'http2'; import type {EsmHmrEngine} from './hmr-server-engine'; // RawSourceMap is inlined here for bundle purposes. // import type {RawSourceMap} from 'source-map'; export interface RawSourceMap { version: number; sources: string[]; names: string[]; sourceRoot?: string; sourcesContent?: string[]; mappings: string; file: string; } export type DeepPartial = { [P in keyof T]?: T[P] extends Array ? Array> : T[P] extends ReadonlyArray ? ReadonlyArray> : DeepPartial; }; export interface ServerRuntimeConfig { config: SnowpackConfig; load: (url: string) => Promise>; } export interface ServerRuntime { importModule: (url: string) => Promise>; invalidateModule: (url: string) => void; } export interface ServerRuntimeModule { exports: T; css: string[]; } export interface LoadResult { contents: T; imports: InstallTarget[]; originalFileLoc: string | null; contentType: string | false; checkStale?: () => Promise; } export type OnFileChangeCallback = ({filePath: string}) => any; export interface LoadUrlOptions { isSSR?: boolean; isHMR?: boolean; isResolve?: boolean; allowStale?: boolean; encoding?: undefined | BufferEncoding | null; importMap?: ImportMap; } export interface SnowpackDevServer { port: number; hmrEngine?: EsmHmrEngine; rawServer?: http.Server | http2.Http2Server | undefined; loadUrl: { (reqUrl: string, opt?: (LoadUrlOptions & {encoding?: undefined}) | undefined): Promise< LoadResult | undefined >; (reqUrl: string, opt: LoadUrlOptions & {encoding: BufferEncoding}): Promise< LoadResult | undefined >; (reqUrl: string, opt: LoadUrlOptions & {encoding: null}): Promise< LoadResult | undefined >; }; handleRequest: ( req: http.IncomingMessage, res: http.ServerResponse, options?: {handleError?: boolean}, ) => Promise; sendResponseFile: ( req: http.IncomingMessage, res: http.ServerResponse, {contents, originalFileLoc, contentType}: LoadResult, ) => void; getServerRuntime: (options?: {invalidateOnChange?: boolean}) => ServerRuntime; sendResponseError: (req: http.IncomingMessage, res: http.ServerResponse, status: number) => void; getUrlForFile: (fileLoc: string) => string | null; getUrlForPackage: (packageSpec: string) => Promise; onFileChange: (callback: OnFileChangeCallback) => void; shutdown(): Promise; markChanged: (fileLoc: string) => void; } export type SnowpackBuildResultFileManifest = Record< string, {source: string | null; contents: string | Buffer} >; export interface SnowpackBuildResult { // result: SnowpackBuildResultFileManifest; onFileChange: (callback: OnFileChangeCallback) => void; shutdown(): Promise; } export type SnowpackBuiltFile = { code: string | Buffer; map?: string; }; export type SnowpackBuildMap = Record; /** Standard file interface */ export interface SnowpackSourceFile { /** base extension (e.g. `.js`) */ baseExt: string; /** file contents */ contents: Type; /** if no location on disk, assume this exists in memory */ locOnDisk: string; /** project "root" directory */ root: string; } export interface PluginLoadOptions { /** The absolute file path of the source file, on disk. */ filePath: string; /** A helper for just the file extension of the source file (ex: ".js", ".svelte") */ fileExt: string; /** True if builder is in dev mode (`snowpack dev` or `snowpack build --watch`) */ isDev: boolean; /** True if HMR is enabled (add any HMR code to the output here). */ isHmrEnabled: boolean; /** True if builder is in SSR mode */ isSSR: boolean; /** True if file being transformed is inside of a package. */ isPackage: boolean; } export interface PluginTransformOptions { /** The final build file path (note: this may differ from the source, e.g. `.vue` will yield `.js` and `.css` IDs) */ id: string; /** The original source location on disk (it may differ from ID) */ srcPath: string; /** The extension of the file */ fileExt: string; /** Contents of the file to transform */ contents: string | Buffer; /** True if builder is in dev mode (`snowpack dev` or `snowpack build --watch`) */ isDev: boolean; /** True if HMR is enabled (add any HMR code to the output here). */ isHmrEnabled: boolean; /** True if builder is in SSR mode */ isSSR: boolean; /** True if file being transformed is inside of a package. */ isPackage: boolean; } export interface PluginRunOptions { isDev: boolean; } /** map of extensions -> code (e.g. { ".js": "[code]", ".css": "[code]" }) */ export type PluginLoadResult = SnowpackBuildMap; export type PluginTransformResult = {contents: string; map: string | RawSourceMap}; export interface PluginOptimizeOptions { buildDirectory: string; } export interface SnowpackPlugin { /** name of the plugin */ name: string; /** Tell Snowpack how the load() function will resolve files. */ resolve?: { /** file extensions that this load function takes as input (e.g. [".jsx", ".js", …]) */ input: string[]; /** file extensions that this load function outputs (e.g. [".js", ".css"]) */ output: string[]; }; /** load a file that matches resolve.input */ load?(options: PluginLoadOptions): Promise; /** transform a file that matches resolve.input */ transform?( options: PluginTransformOptions, ): Promise; /** runs a command, unrelated to file building (e.g. TypeScript, ESLint) */ run?(options: PluginRunOptions): Promise; /** optimize the entire built application */ optimize?(options: PluginOptimizeOptions): Promise; /** cleanup any long-running instances/services before exiting. */ cleanup?(): void | Promise; /** Known dependencies that should be installed */ knownEntrypoints?: string[]; /** read and modify the Snowpack config object */ config?(snowpackConfig: SnowpackConfig): void; /** Called when a watched file changes during development. */ onChange?({filePath}: {filePath: string}): void; /** (internal interface, not set by the user) Mark a file as changed. */ markChanged?(file: string): void; } /** Snowpack Build Plugin type */ export type SnowpackPluginFactory = ( snowpackConfig: SnowpackConfig, pluginOptions?: PluginOptions, ) => SnowpackPlugin; export type MountEntry = { url: string; static: boolean; resolve: boolean; dot: boolean; }; export interface OptimizeOptions { entrypoints: 'auto' | string[] | ((options: {files: string[]}) => string[]); preload: boolean; bundle: boolean; loader?: {[ext: string]: Loader}; sourcemap: boolean | 'both' | 'inline' | 'external'; splitting: boolean; treeshake: boolean; manifest: boolean; minify: boolean; target: 'es2020' | 'es2019' | 'es2018' | 'es2017'; } export interface RouteConfigObject { src: string; dest: string | ((req: http.IncomingMessage, res: http.ServerResponse) => void) | undefined; upgrade: ((req: http.IncomingMessage, socket: net.Socket, head: Buffer) => void) | undefined; match: 'routes' | 'all'; _srcRegex: RegExp; } export interface PackageOptionsLocal extends Omit< EsinstallOptions, 'alias' | 'dest' | 'sourcemap' | 'verbose' | 'logger' | 'cwd' | 'dest' | 'treeshake' > { source: 'local' | 'remote-next' | {[key: string]: string}; external: string[]; knownEntrypoints: string[]; } export interface PackageOptionsRemote { source: 'remote'; external: string[]; knownEntrypoints: string[]; origin: string; cache: string; types: boolean; } // interface this library uses internally export interface SnowpackConfig { root: string; mode: 'test' | 'development' | 'production'; workspaceRoot?: string | false; extends?: string; exclude: string[]; env?: Record; mount: Record; alias: Record; plugins: SnowpackPlugin[]; dependencies: Record; devOptions: { secure: boolean | {cert: string | Buffer; key: string | Buffer}; hostname: string; port: number; openUrl?: string; open?: string; output?: 'stream' | 'dashboard'; hmr?: boolean; hmrDelay: number; hmrPort: number | undefined; hmrErrorOverlay: boolean; tailwindConfig?: string; }; buildOptions: { out: string; baseUrl: string; metaUrlPath: string; cacheDirPath: string; clean: boolean; sourcemap: 'inline' | false | undefined; watch: boolean; htmlFragments: boolean; jsxFactory: string | undefined; jsxFragment: string | undefined; jsxInject: string | undefined; ssr: boolean; resolveProxyImports: boolean; }; testOptions: { files: string[]; }; packageOptions: PackageOptionsLocal | PackageOptionsRemote; /** Optimize your site for production. */ optimize?: OptimizeOptions; /** Configure routes during development. */ routes: RouteConfigObject[]; /** EXPERIMENTAL - This section is experimental and not yet finalized. May change across minor versions. */ experiments: { /* intentionally left blank */ }; _extensionMap: Record; } export type SnowpackUserConfig = { root?: string; mode?: SnowpackConfig['mode']; workspaceRoot?: string; install?: string[]; env?: Record; extends?: string; exclude?: string[]; mount?: Record>; alias?: Record; plugins?: (string | [string, any])[]; dependencies?: Record; devOptions?: Partial; buildOptions?: Partial; testOptions?: Partial; packageOptions?: Partial; optimize?: Partial; routes?: Pick[]; experiments?: { /* intentionally left blank */ }; }; export interface CLIFlags { help?: boolean; // display help text version?: boolean; // display Snowpack version reload?: boolean; root?: string; // manual path to project root config?: string; // manual path to config file env?: string[]; // env vars open?: string[]; secure?: boolean; verbose?: boolean; quiet?: boolean; [flag: string]: any; } export interface ImportMap { imports: {[specifier: string]: string}; } export interface LockfileManifest { dependencies: {[packageName: string]: string}; lock: {[specifier: string]: string}; } export interface CommandOptions { config: SnowpackConfig; lockfile?: LockfileManifest | null; } export type LoggerLevel = 'debug' | 'info' | 'warn' | 'error' | 'silent'; // same as Pino export type LoggerEvent = 'debug' | 'info' | 'warn' | 'error'; export interface LoggerOptions { /** (optional) change name at beginning of line */ name?: string; /** (optional) do some additional work after logging a message, if log level is enabled */ task?: Function; } /** PackageSource - a common interface for loading and interacting with dependencies. */ export interface PackageSource { /** * Do any work needed before starting the dev server or build. Either will wait * for this to complete before continuing. Example: For "local", this involves * running esinstall (if needed) to prepare your local dependencies as ESM. */ prepare(): Promise; /** * Like prepare(), but only looks at a single file and meant to run at anytime, * usually after the server has already started and is running. */ prepareSingleFile(fileLoc: string): Promise; /** * Load a dependency with the given spec (ex: "/pkg/react" -> "react") * If load fails or is unsuccessful, reject the promise. */ load( spec: string, options: {isSSR: boolean}, ): Promise; /** Resolve a package import to URL (ex: "react" -> "/pkg/react") */ resolvePackageImport( spec: string, options?: {source?: string; importMap?: ImportMap; depth?: number}, ): Promise; /** Modify the build install config for optimized build install. */ modifyBuildInstallOptions( installOptions: EsinstallOptions, installTargets: InstallTarget[], ): Promise; getCacheFolder(): string; clearCache(): void | Promise; } export type ScannableExt = | '.astro' | '.cjs' | '.css' | '.html' | '.interface' | '.js' | '.jsx' | '.less' | '.mjs' | '.sass' | '.scss' | '.svelte' | '.ts' | '.tsx' | '.vue'; ================================================ FILE: snowpack/src/util.ts ================================================ import etag from 'etag'; import execa from 'execa'; import findUp from 'find-up'; import fs from 'fs'; import {isBinaryFile} from 'isbinaryfile'; import mkdirp from 'mkdirp'; import open from 'open'; import path from 'path'; import rimraf from 'rimraf'; import url from 'url'; import getDefaultBrowserId from 'default-browser-id'; import type {ImportMap, LockfileManifest, SnowpackConfig} from './types'; import type {InstallTarget} from 'esinstall'; import {SkypackSDK} from 'skypack'; import {REMOTE_PACKAGE_ORIGIN} from './config'; import {GLOBAL_CACHE_DIR} from './sources/util'; // (!) Beware circular dependencies! No relative imports! // Because this file is imported from so many different parts of Snowpack, // importing other relative files inside of it is likely to introduce broken // circular dependencies (sometimes only visible in the final bundled build.) export const IS_DOTFILE_REGEX = /\/\.[^\/]+$/; // note: always assume forward-slashes, even on Windows export const LOCKFILE_NAME = 'snowpack.deps.json'; // We need to use eval here to prevent Rollup from detecting this use of `require()` export const NATIVE_REQUIRE = eval('require'); // We need to use an external file here to prevent Typescript/Rollup from modifying `require` and `import` // NOTE: revisit this when `node@10` reaches EOL. Can we move everything to ESM and just use `import`? export const REQUIRE_OR_IMPORT: ( id: string, opts?: {from?: string}, ) => Promise = require('../../assets/require-or-import.js'); export function createRemotePackageSDK(config: SnowpackConfig) { // This should only be called when config.packageOptions.source is 'remote'. if (config.packageOptions.source !== 'remote') { throw new Error('expected "remote" packageOptions.source'); } // For consistency with previous behavior, we default to REMOTE_PACKAGE_ORIGIN // if no origin is provided. We could simply leave it undefined and allow // SkypackSDK to use its own default. return new SkypackSDK({ origin: config.packageOptions.origin || REMOTE_PACKAGE_ORIGIN, }); } // A note on cache naming/versioning: We currently version our global caches // with the version of the last breaking change. This allows us to re-use the // same cache across versions until something in the data structure changes. // At that point, bump the version in the cache name to create a new unique // cache name. export const BUILD_CACHE = path.join(GLOBAL_CACHE_DIR, 'build-cache-2.7'); const LOCKFILE_HASH_FILE = '.hash'; // NOTE(fks): Must match empty script elements to work properly. export const HTML_JS_REGEX = /(]*?type="module".*?>)(.*?)<\/script>/gims; export const HTML_STYLE_REGEX = /()(.*?)<\/style>/gims; export const CSS_REGEX = /@import\s*['"](.*?)['"];/gs; export const SVELTE_VUE_REGEX = /(]*>)(.*?)<\/script>/gims; export const ASTRO_REGEX = /---(.*?)---/gims; export function getCacheKey(fileLoc: string, {isSSR, mode}) { return `${fileLoc}?mode=${mode}&isSSR=${isSSR ? '1' : '0'}`; } export type Awaited = T extends PromiseLike ? Awaited : T; /** * Like rimraf, but will fail if "dir" is outside of your configured build output directory. */ export function deleteFromBuildSafe(dir: string, config: SnowpackConfig) { const {out} = config.buildOptions; if (!path.isAbsolute(dir)) { throw new Error(`rimrafSafe(): dir ${dir} must be a absolute path`); } if (!path.isAbsolute(out)) { throw new Error(`rimrafSafe(): buildOptions.out ${out} must be a absolute path`); } if (!dir.startsWith(out)) { throw new Error(`rimrafSafe(): ${dir} outside of buildOptions.out ${out}`); } return rimraf.sync(dir); } /** Read file from disk; return a string if it’s a code file */ export async function readFile(filepath: string): Promise { let data = await fs.promises.readFile(filepath); if (!data) { console.error( `Unexpected Node.js error: readFile(${filepath}) returned undefined.\n\n` + `Somehow in Github CI / Jest its possible for fs.promises.readFile to return undefined.\n` + `This should be impossible, and has not yet been reproduced in the real world, but we do see it in our own CI.\n` + `If you are seeing this error, please report!`, ); data = fs.readFileSync(filepath); } const isBinary = await isBinaryFile(data); return isBinary ? data : data.toString('utf8'); } export async function readLockfile(cwd: string): Promise { try { var lockfileContents = fs.readFileSync(path.join(cwd, LOCKFILE_NAME), { encoding: 'utf8', }); } catch (err) { // no lockfile found, ignore and continue return null; } // If this fails, we actually do want to alert the user by throwing return JSON.parse(lockfileContents); } export function createInstallTarget(specifier: string, all = true): InstallTarget { return { specifier, all, default: false, namespace: false, named: [], }; } function sortObject(originalObject: Record): Record { const newObject = {}; for (const key of Object.keys(originalObject).sort()) { newObject[key] = originalObject[key]; } return newObject; } export function convertLockfileToSkypackImportMap( origin: string, lockfile: LockfileManifest, ): ImportMap { const result = {imports: {}}; for (const [key, val] of Object.entries(lockfile.lock)) { result.imports[key.replace(/\#.*/, '')] = origin + '/' + val; result.imports[key.replace(/\#.*/, '') + '/'] = origin + '/' + val + '/'; } return result; } export function convertSkypackImportMapToLockfile( dependencies: Record, importMap: ImportMap, ): LockfileManifest { const result = {dependencies, lock: {}}; for (const [key, val] of Object.entries(dependencies)) { if (importMap.imports[key]) { const valPath = url.parse(importMap.imports[key]).pathname; result.lock[key + '#' + val] = valPath?.substr(1); } } return result; } export async function writeLockfile(loc: string, importMap: LockfileManifest): Promise { importMap.dependencies = sortObject(importMap.dependencies); importMap.lock = sortObject(importMap.lock); fs.writeFileSync(loc, JSON.stringify(importMap, undefined, 2), {encoding: 'utf8'}); } export function isTruthy(item: T | false | null | undefined): item is T { return Boolean(item); } /** * Returns true if fsevents exists. When Snowpack is bundled, automatic fsevents * detection fails for many libraries. This function helps add back support. */ export function isFsEventsEnabled(): boolean { try { NATIVE_REQUIRE('fsevents'); return true; } catch (e) { return false; } } /** Get the package name + an entrypoint within that package (if given). */ export function parsePackageImportSpecifier(imp: string): [string, string | null] { const impParts = imp.split('/'); if (imp.startsWith('@')) { const [scope, name, ...rest] = impParts; return [`${scope}/${name}`, rest.join('/') || null]; } const [name, ...rest] = impParts; return [name, rest.join('/') || null]; } /** * Given a package name, look for that package's package.json manifest. * Return both the manifest location (if believed to exist) and the * manifest itself (if found). * * NOTE: You used to be able to require() a package.json file directly, * but now with export map support in Node v13 that's no longer possible. */ export function resolveDependencyManifest(dep: string, cwd: string): [string | null, any | null] { // Attempt #1: Resolve the dependency manifest normally. This works for most // packages, but fails when the package defines an export map that doesn't // include a package.json. If we detect that to be the reason for failure, // move on to our custom implementation. try { const depManifest = fs.realpathSync.native( require.resolve(`${dep}/package.json`, {paths: [cwd]}), ); return [depManifest, NATIVE_REQUIRE(depManifest)]; } catch (err) { // if its an export map issue, move on to our manual resolver. if (err.code !== 'ERR_PACKAGE_PATH_NOT_EXPORTED') { return [null, null]; } } // Attempt #2: Resolve the dependency manifest manually. This involves resolving // the dep itself to find the entrypoint file, and then haphazardly replacing the // file path within the package with a "./package.json" instead. It's not as // thorough as Attempt #1, but it should work well until export maps become more // established & move out of experimental mode. let result = [null, null] as [string | null, any | null]; try { const fullPath = fs.realpathSync.native(require.resolve(dep, {paths: [cwd]})); // Strip everything after the package name to get the package root path // NOTE: This find-replace is very gross, replace with something like upath. const searchPath = `${path.sep}node_modules${path.sep}${dep.replace('/', path.sep)}`; const indexOfSearch = fullPath.lastIndexOf(searchPath); if (indexOfSearch >= 0) { const manifestPath = fullPath.substring(0, indexOfSearch + searchPath.length + 1) + 'package.json'; result[0] = manifestPath; const manifestStr = fs.readFileSync(manifestPath, {encoding: 'utf8'}); result[1] = JSON.parse(manifestStr); } } catch (err) { // ignore } finally { return result; } } /** * If Rollup erred parsing a particular file, show suggestions based on its * file extension (note: lowercase is fine). */ export const MISSING_PLUGIN_SUGGESTIONS: {[ext: string]: string} = { '.svelte': 'Try installing rollup-plugin-svelte and adding it to Snowpack (https://www.snowpack.dev/tutorials/svelte)', '.vue': 'Try installing rollup-plugin-vue and adding it to Snowpack (https://www.snowpack.dev/guides/vue)', }; const appNames = { win32: { brave: 'brave', }, darwin: { brave: 'Brave Browser', }, linux: { brave: 'brave', }, }; async function openInExistingChromeBrowser(url: string) { // see if Chrome process is open; fail if not await execa.command('ps cax | grep "Google Chrome"', { shell: true, }); // use open Chrome tab if exists; create new Chrome tab if not const openChrome = execa( 'osascript ../../assets/openChrome.appleScript "' + encodeURI(url) + '"', { cwd: __dirname, stdio: 'ignore', shell: true, }, ); // if Chrome doesn’t respond within 3s, fall back to opening new tab in default browser let isChromeStalled = setTimeout(() => { openChrome.cancel(); }, 3000); try { await openChrome; } catch (err) { if (err.isCanceled) { console.warn(`Chrome not responding to Snowpack after 3s. Opening in new tab.`); } else { console.error(err.toString() || err); } throw err; } finally { clearTimeout(isChromeStalled); } } export async function openInBrowser( protocol: string, hostname: string, port: number, browser: string, openUrl?: string, ): Promise { const url = new URL(openUrl || '', `${protocol}//${hostname}:${port}`).toString(); if (/chrome/i.test(browser)) { browser = open.apps.chrome as string; } if (/brave/i.test(browser)) { browser = appNames[process.platform]['brave']; } const isMacChrome = process.platform === 'darwin' && (/chrome/i.test(browser) || (/default/i.test(browser) && /chrome/i.test(await getDefaultBrowserId()))); if (!isMacChrome) { await (browser === 'default' ? open(url) : open(url, {app: {name: browser}})); return; } try { // If we're on macOS, and we haven't requested a specific browser, // we can try opening Chrome with AppleScript. This lets us reuse an // existing tab when possible instead of creating a new one. await openInExistingChromeBrowser(url); } catch (err) { // if no open Chrome process, just go ahead and open default browser. await open(url); } } export async function checkLockfileHash(dir: string) { const lockfileLoc = await findUp(['package-lock.json', 'yarn.lock']); if (!lockfileLoc) { return true; } const hashLoc = path.join(dir, LOCKFILE_HASH_FILE); const newLockHash = etag(await fs.promises.readFile(lockfileLoc, 'utf8')); const oldLockHash = await fs.promises.readFile(hashLoc, 'utf8').catch(() => ''); return newLockHash === oldLockHash; } export async function updateLockfileHash(dir: string) { const lockfileLoc = await findUp(['package-lock.json', 'yarn.lock']); if (!lockfileLoc) { return; } const hashLoc = path.join(dir, LOCKFILE_HASH_FILE); const newLockHash = etag(await fs.promises.readFile(lockfileLoc)); await mkdirp(path.dirname(hashLoc)); await fs.promises.writeFile(hashLoc, newLockHash); } function getAliasType(val: string): 'package' | 'path' | 'url' { if (isRemoteUrl(val)) { return 'url'; } return !path.isAbsolute(val) ? 'package' : 'path'; } /** * For the given import specifier, return an alias entry if one is matched. */ export function findMatchingAliasEntry( config: SnowpackConfig, spec: string, ): {from: string; to: string; type: 'package' | 'path' | 'url'} | undefined { // Only match bare module specifiers. relative and absolute imports should not match if (isPathImport(spec) || isRemoteUrl(spec)) { return undefined; } for (const [from, to] of Object.entries(config.alias)) { const isExactMatch = spec === from; const isDeepMatch = spec.startsWith(addTrailingSlash(from)); if (isExactMatch || isDeepMatch) { return { from, to, type: getAliasType(to), }; } } } /** * Get the most specific file extension match possible. */ export function getExtensionMatch( fileName: string, extensionMap: Record, ): [string, string[]] | undefined { let extensionPartial; let extensionMatch; // If a full URL is given, start at the basename. Otherwise, start at zero. let extensionMatchIndex = Math.max(0, fileName.lastIndexOf('/'), fileName.lastIndexOf('\\')); // Grab expanded file extensions, from longest to shortest. while (!extensionMatch && extensionMatchIndex > -1) { extensionMatchIndex++; extensionMatchIndex = fileName.indexOf('.', extensionMatchIndex); extensionPartial = fileName.substr(extensionMatchIndex).toLowerCase(); extensionMatch = extensionMap[extensionPartial]; } // Return the first match, if one was found. Otherwise, return undefined. return extensionMatch ? [extensionPartial, extensionMatch] : undefined; } export function isPathImport(spec: string): boolean { return spec[0] === '.' || spec[0] === '/'; } export function isRemoteUrl(val: string): boolean { return val.startsWith('//') || !!url.parse(val).protocol?.startsWith('http'); } export function isImportOfPackage(importUrl: string, packageName: string) { return packageName === importUrl || importUrl.startsWith(packageName + '/'); } /** * Sanitizes npm packages that end in .js (e.g `tippy.js` -> `tippyjs`). * This is necessary because Snowpack can’t create both a file and directory * that end in .js. */ export function sanitizePackageName(filepath: string): string { const dirs = filepath.split('/'); const file = dirs.pop() as string; return [...dirs.map((path) => path.replace(/\.js$/i, 'js')), file].join('/'); } // Source Map spec v3: https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit#heading=h.lmz475t4mvbx /** CSS sourceMappingURL */ export function cssSourceMappingURL(code: string, sourceMappingURL: string) { return code + `/*# sourceMappingURL=${sourceMappingURL} */`; } /** JS sourceMappingURL */ export function jsSourceMappingURL(code: string, sourceMappingURL: string) { return code.replace(/\n*$/, '') + `\n//# sourceMappingURL=${sourceMappingURL}\n`; // strip ending lines & append source map (with linebreaks for safety) } /** URL relative */ export function relativeURL(path1: string, path2: string): string { let url = path.relative(path1, path2).replace(/\\/g, '/'); if (!url.startsWith('./') && !url.startsWith('../')) { url = './' + url; } return url; } const CLOSING_HEAD_TAG = /<\s*\/\s*head\s*>/gi; /** Append HTML before closing tag */ export function appendHtmlToHead(doc: string, htmlToAdd: string) { const closingHeadMatch = doc.match(CLOSING_HEAD_TAG); // if no tag found, throw an error (we can’t load your app properly) if (!closingHeadMatch) { throw new Error(`No tag found in HTML (this is needed to optimize your app):\n${doc}`); } // if multiple tags found, also freak out if (closingHeadMatch.length > 1) { throw new Error(`Multiple tags found in HTML (perhaps commented out?):\n${doc}`); } return doc.replace(closingHeadMatch[0], htmlToAdd + closingHeadMatch[0]); } export function isJavaScript(pathname: string): boolean { const ext = path.extname(pathname).toLowerCase(); return ext === '.js' || ext === '.mjs' || ext === '.cjs'; } export function getExtension(str: string) { return path.extname(str).toLowerCase(); } export function hasExtension(str: string, ext: string) { return new RegExp(`\\${ext}$`, 'i').test(str); } export function replaceExtension(fileName: string, oldExt: string, newExt: string): string { const extToReplace = new RegExp(`\\${oldExt}$`, 'i'); return fileName.replace(extToReplace, newExt); } export function addExtension(fileName: string, newExt: string): string { return fileName + newExt; } export function removeExtension(fileName: string, oldExt: string): string { return replaceExtension(fileName, oldExt, ''); } /** Add / to beginning of string (but don’t double-up) */ export function addLeadingSlash(path: string) { return path.replace(/^\/?/, '/'); } /** Add / to the end of string (but don’t double-up) */ export function addTrailingSlash(path: string) { return path.replace(/\/?$/, '/'); } /** Remove \ and / from beginning of string */ export function removeLeadingSlash(path: string) { return path.replace(/^[/\\]+/, ''); } /** Remove \ and / from end of string */ export function removeTrailingSlash(path: string) { return path.replace(/[/\\]+$/, ''); } /** It's `Array.splice`, but for Strings! */ export function spliceString(source: string, withSlice: string, start: number, end: number) { return source.slice(0, start) + (withSlice || '') + source.slice(end); } export const HMR_CLIENT_CODE = fs.readFileSync( path.resolve(__dirname, '../../assets/hmr-client.js'), 'utf8', ); export const HMR_OVERLAY_CODE = fs.readFileSync( path.resolve(__dirname, '../../assets/hmr-error-overlay.js'), 'utf8', ); export const INIT_TEMPLATE_FILE = fs.readFileSync( path.resolve(__dirname, '../../assets/snowpack-init-file.js'), 'utf8', ); ================================================ FILE: snowpack/tsconfig.cjs.json ================================================ { "extends": "./tsconfig.json", "compilerOptions": { "declaration": true, "module": "CommonJS", "outDir": "lib/cjs" } } ================================================ FILE: snowpack/tsconfig.json ================================================ { "include": ["src", "assets/require-or-import.ts"], "exclude": ["lib"], "compilerOptions": { "outDir": "lib/esm", "module": "ESNext", "target": "es2018", "moduleResolution": "node", "declaration": true, "esModuleInterop": true, "strict": true, "sourceMap": false, "noImplicitAny": false, "noUnusedLocals": true, "noUnusedParameters": true, "skipLibCheck": true } } ================================================ FILE: test/build/config-loading-esm-package/config-loading-esm-package.test.js ================================================ const fs = require('fs'); const path = require('path'); const {setupBuildTest} = require('../../test-utils'); const cwd = path.join(__dirname, 'TEST_BUILD_OUT'); // Skip tests on node@10.x (expected to fail) describe = process.version.startsWith('v10') ? describe.skip : describe; describe('config-loading: detects snowpack.config.js inside of "type": "module" package', () => { beforeAll(() => { setupBuildTest(__dirname); }); it('picks up on configured buildOptions.out', () => { const distJSLoc = path.join(cwd, 'src', 'index.js'); expect(fs.existsSync(distJSLoc)).toBe(true); // JS file exists const snowpackMetaLoc = path.join(cwd, '_snowpack', 'env.js'); expect(fs.existsSync(snowpackMetaLoc)).toBe(true); // snowpack meta exists }); }); ================================================ FILE: test/build/config-loading-esm-package/package.json ================================================ { "private": true, "version": "0.1.0", "type": "module", "name": "@snowpack/test-config-loading-esm-package", "scripts": { "testbuild": "snowpack build" }, "dependencies": { "snowpack": "^3.8.8" } } ================================================ FILE: test/build/config-loading-esm-package/snowpack.config.js ================================================ export default { buildOptions: { out: 'TEST_BUILD_OUT', }, }; ================================================ FILE: test/build/config-loading-esm-package/src/index.js ================================================ console.log(import.meta.env); ================================================ FILE: test/build/config-loading-mjs/config-loading-mjs.test.js ================================================ const fs = require('fs'); const path = require('path'); const {setupBuildTest} = require('../../test-utils'); const cwd = path.join(__dirname, 'TEST_BUILD_OUT'); // Skip tests on node@10.x (expected to fail) describe = process.version.startsWith('v10') ? describe.skip : describe; describe('config-loading: detects snowpack.config.mjs', () => { beforeAll(() => { setupBuildTest(__dirname); }); it('picks up on configured buildOptions.out', () => { const distJSLoc = path.join(cwd, 'src', 'index.js'); expect(fs.existsSync(distJSLoc)).toBe(true); // JS file exists const snowpackMetaLoc = path.join(cwd, '_snowpack', 'env.js'); expect(fs.existsSync(snowpackMetaLoc)).toBe(true); // snowpack meta exists }); }); ================================================ FILE: test/build/config-loading-mjs/package.json ================================================ { "private": true, "version": "0.1.0", "name": "@snowpack/test-config-loading-mjs", "scripts": { "testbuild": "snowpack build" }, "dependencies": { "snowpack": "^3.8.8" } } ================================================ FILE: test/build/config-loading-mjs/snowpack.config.mjs ================================================ export default { buildOptions: { "out": "TEST_BUILD_OUT" } } ================================================ FILE: test/build/config-loading-mjs/src/index.js ================================================ console.log(import.meta.env); ================================================ FILE: test/build/config-path/config-path.test.js ================================================ const path = require('path'); const {setupBuildTest, readFiles} = require('../../test-utils'); const cwd = path.join(__dirname, 'build'); let files = {}; describe('snowpack build --config path', () => { beforeAll(() => { setupBuildTest(__dirname); files = readFiles(cwd); }); it('loads correctly', () => { expect(files['/index.js']).toBeTruthy(); }); }); ================================================ FILE: test/build/config-path/index.js ================================================ export default function () {} ================================================ FILE: test/build/config-path/my-config-file.js ================================================ module.exports = {}; ================================================ FILE: test/build/config-path/package.json ================================================ { "name": "@snowpackjs/test-config-path", "description": "Passing --config", "version": "1.0.0", "main": "index.js", "scripts": { "testbuild": "snowpack build --config my-config-file.js" } } ================================================ FILE: test/build/entrypoint-ids/entrypoint-ids.test.js ================================================ const os = require('os'); const path = require('path'); const {setupBuildTest, readFiles} = require('../../test-utils'); const IMPORTS = ['ansi-styles', 'chalk']; const cwd = path.join(__dirname, 'build'); let files = {}; describe('core: pkg resolution', () => { beforeAll(() => { const capitalize = os.platform() === 'win32'; // for Windows, we capitalize this one directory to see if Snowpack can still resolve setupBuildTest(capitalize ? __dirname.toUpperCase() : __dirname); files = readFiles(cwd); }); it('resolves pkg without case-sensitivity', () => { IMPORTS.forEach((i) => { expect(files['/_dist_/index.js']).toEqual( expect.stringContaining(`import '../_snowpack/pkg/${i}.js';`), ); }); }); }); ================================================ FILE: test/build/entrypoint-ids/package.json ================================================ { "private": true, "version": "1.0.0", "name": "@snowpack/test-entrypoint-ids", "description": "Tests that a non-canonical cwd works on case-insensitive file systems", "scripts": { "testbuild": "snowpack build" }, "snowpack": { "mount": { "./src": "/_dist_" } }, "dependencies": { "chalk": "4.1.0", "ansi-styles": "*" }, "devDependencies": { "snowpack": "^3.8.8" } } ================================================ FILE: test/build/entrypoint-ids/src/index.js ================================================ import 'chalk'; import 'ansi-styles'; ================================================ FILE: test/build/import-assets/import-assets.test.js ================================================ const path = require('path'); const {setupBuildTest, readFiles} = require('../../test-utils'); const cwd = path.join(__dirname, 'build'); let files = {}; describe('import resource', () => { beforeAll(() => { setupBuildTest(__dirname); files = readFiles(cwd); }); describe('css', () => { it('proxy is created', () => { expect(files['/_dist_/styles.css.proxy.js']).toEqual( expect.stringContaining(`font-family: fantasy;`), ); }); it('proxy is resolved in JS', () => { expect(files['/_dist_/index.js']).toEqual( expect.stringContaining(`import './styles.css.proxy.js';`), ); }); }); describe('image', () => { it('proxy is resolved in JS', () => { expect(files['/_dist_/index.js']).toEqual( expect.stringContaining(`import './logo.png.proxy.js';`), ); }); }); describe('os compat', () => { // note: this test isn‘t aware of OS; it’s just something to run in multiple environments it('there are no backslashes anywhere in Windows', () => { expect(files['/_dist_/index.js']).not.toEqual(expect.stringContaining(`\\`)); }); }); }); ================================================ FILE: test/build/import-assets/package.json ================================================ { "private": true, "version": "1.0.1", "name": "@snowpack/test-import-css", "description": "A test to make sure that URLs for resources don’t have backslashes on Windows", "scripts": { "start": "snowpack dev", "testbuild": "snowpack build" }, "devDependencies": { "snowpack": "^3.8.8" } } ================================================ FILE: test/build/import-assets/snowpack.config.json ================================================ { "mount": { "./src": "/_dist_" } } ================================================ FILE: test/build/import-assets/src/index.js ================================================ import './styles.css'; import './logo.png'; console.log('loaded'); ================================================ FILE: test/build/import-assets/src/styles.css ================================================ body { font-family: fantasy; } ================================================ FILE: test/build/package-bootstrap/package-bootstrap.test.js ================================================ const path = require('path'); const {setupBuildTest, readFiles} = require('../../test-utils'); const cwd = path.join(__dirname, 'build'); let files = {}; describe('package: bootstrap', () => { beforeAll(() => { setupBuildTest(__dirname); files = readFiles(cwd); }); it('resolves JS', () => { expect(files['/_dist_/index.js']).toEqual( expect.stringContaining( `import '../_snowpack/pkg/bootstrap/dist/css/bootstrap.min.css.proxy.js';`, ), ); }); }); ================================================ FILE: test/build/package-bootstrap/package.json ================================================ { "private": true, "version": "1.0.1", "name": "@snowpack/test-package-bootstrap", "description": "Tests that CSS proxy files are properly imported from _snowpack/pkg", "scripts": { "testbuild": "snowpack build" }, "snowpack": { "mount": { "./src": "/_dist_" } }, "dependencies": { "bootstrap": "^4.5.2" }, "devDependencies": { "snowpack": "^3.8.8" } } ================================================ FILE: test/build/package-bootstrap/src/index.js ================================================ import 'bootstrap/dist/css/bootstrap.min.css'; console.log('CSS added to page!'); ================================================ FILE: test/build/package-bootstrap/src/styles.css ================================================ @import "bootstrap/dist/css/bootstrap.min.css"; ================================================ FILE: test/build/package-workspace/package-workspace.test.js ================================================ const fs = require('fs'); const path = require('path'); const {setupBuildTest} = require('../../test-utils'); const cwd = path.join(__dirname, 'build'); describe('test workspace linked packages', () => { beforeAll(() => { setupBuildTest(__dirname); }); it('builds source files as expected', () => { const jsLoc = path.join(cwd, '_dist_', 'index.svelte.js'); expect(fs.existsSync(jsLoc)).toBe(true); // file exists expect(fs.readFileSync(jsLoc, 'utf-8')).toContain( `../_snowpack/link/test-workspace-component/SvelteComponent.svelte.js`, ); // file has expected imports expect(fs.readFileSync(jsLoc, 'utf-8')).toContain( `../_snowpack/link/test-workspace-component/works-without-extension.js`, ); // file has expected imports }); it('builds workspace package files as expected', () => { expect( fs.existsSync( path.join( cwd, '_snowpack', 'link', 'test-workspace-component', 'SvelteComponent.svelte.js', ), ), ).toBe(true); // import exists expect( fs.existsSync( path.join( cwd, '_snowpack', 'link', 'test-workspace-component', 'SvelteComponent.svelte.css.proxy.js', ), ), ).toBe(true); // import exists }); }); ================================================ FILE: test/build/package-workspace/package.json ================================================ { "private": true, "version": "1.0.1", "name": "@snowpack/test-package-workspace", "description": "A test to make sure linked packages are supported", "scripts": { "testbuild": "snowpack build" }, "devDependencies": { "snowpack": "^3.8.8" }, "dependencies": { "test-workspace-component": "^1.0.0" } } ================================================ FILE: test/build/package-workspace/snowpack.config.js ================================================ /** @type {import("snowpack").SnowpackUserConfig } */ module.exports = { workspaceRoot: '../', buildOptions: { out: './build', }, mount: { src: '/_dist_', }, plugins: [['@snowpack/plugin-svelte']], }; ================================================ FILE: test/build/package-workspace/src/index.html ================================================ Snowpack App ================================================ FILE: test/build/package-workspace/src/index.svelte ================================================ ================================================ FILE: test/build/plugin-build-script/package.json ================================================ { "private": true, "version": "1.0.1", "name": "@snowpack/test-plugin-build-script", "description": "A test to make sure @snowpack/plugin-build-script works", "scripts": { "testbuild": "snowpack build" }, "devDependencies": { "@babel/cli": "^7.10.5", "@snowpack/plugin-build-script": "^2.0.0", "snowpack": "^3.8.8" }, "dependencies": { "@babel/preset-typescript": "^7.13.0" } } ================================================ FILE: test/build/plugin-build-script/plugin-build-script.test.js ================================================ const fs = require('fs'); const path = require('path'); const {setupBuildTest} = require('../../test-utils'); const cwd = path.join(__dirname, 'build'); describe('@snowpack/plugin-build-script', () => { beforeAll(() => { setupBuildTest(__dirname); }); it('runs Babel on TS', () => { const jsLoc = path.join(cwd, '_dist_', 'index.js'); expect(fs.existsSync(jsLoc)).toBe(true); // file exists expect(fs.readFileSync(jsLoc, 'utf-8')).toBeTruthy(); // file has content }); it('doesn’t leave TS in build', () => { const tsLoc = path.join(cwd, '_dist_', 'index.ts'); expect(fs.existsSync(tsLoc)).not.toBe(true); // file doesn’t exist }); }); ================================================ FILE: test/build/plugin-build-script/snowpack.config.js ================================================ module.exports = { mount: { src: '/_dist_', }, plugins: [ [ '@snowpack/plugin-build-script', { input: ['.ts'], output: ['.js'], cmd: 'babel --filename $FILE --presets @babel/preset-typescript', }, ], ], }; ================================================ FILE: test/build/plugin-build-script/src/index.ts ================================================ type stringType = string; const msg: stringType = 'I’m a TypeScript file'; console.log(msg); ================================================ FILE: test/build/plugin-hook-optimize/custom-optimize-plugin.js ================================================ const fs = require('fs').promises; const path = require('path'); module.exports = function () { return { optimize: async ({buildDirectory}) => { await fs.writeFile(path.join(buildDirectory, 'artifact.txt'), 'TEST: Directory optimized!'); }, }; }; ================================================ FILE: test/build/plugin-hook-optimize/package.json ================================================ { "private": true, "version": "1.0.1", "name": "@snowpack/test-plugin-hook-optimize", "description": "A test to make sure that the plugin optimize() hook is working as expected", "scripts": { "start": "snowpack dev", "testbuild": "snowpack build" }, "devDependencies": { "@snowpack/plugin-optimize": "^0.2.0", "snowpack": "^3.8.8" } } ================================================ FILE: test/build/plugin-hook-optimize/plugin-hook-optimize.test.js ================================================ const fs = require('fs'); const path = require('path'); const glob = require('glob'); const {setupBuildTest} = require('../../test-utils'); describe('plugin API: optimize()', () => { beforeAll(() => { setupBuildTest(__dirname); }); it('generates artifact.txt', () => { // test 1: artifact.txt doesn’t exist in src const files = glob.sync(path.join(__dirname, 'src'), {nodir: true}); expect(files.join(',')).not.toEqual(expect.stringContaining('artifact.txt')); // note: this is a simple check if `artifact.txt` occurs in any part of a filename here // test 2: artifact.txt is generated by optimize() plugin hook const artifactLoc = path.join(__dirname, 'build', 'artifact.txt'); expect(fs.existsSync(artifactLoc)).toBe(true); }); }); ================================================ FILE: test/build/plugin-hook-optimize/snowpack.config.json ================================================ { "mount": { "./src": "/_dist_" }, "plugins": ["./custom-optimize-plugin.js"] } ================================================ FILE: test/build/plugin-hook-optimize/src/index.js ================================================ import './styles.css'; import './logo.png'; import iconComponent from './icon.svg'; console.log('loaded'); ================================================ FILE: test/build/plugin-hook-optimize/src/styles.css ================================================ body { font-family: fantasy; } ================================================ FILE: test/build/plugin-run-script/package.json ================================================ { "private": true, "version": "1.0.1", "name": "@snowpack/test-plugin-run-script", "description": "A test to make sure @snowpack/plugin-run-script works", "scripts": { "testbuild": "snowpack build" }, "devDependencies": { "@snowpack/plugin-run-script": "^2.0.0", "sass": "^1.26.10", "snowpack": "^3.8.8" } } ================================================ FILE: test/build/plugin-run-script/plugin-run-script.test.js ================================================ const fs = require('fs'); const path = require('path'); const {setupBuildTest} = require('../../test-utils'); const cwd = path.join(__dirname, 'build'); describe('@snowpack/plugin-run-script', () => { beforeAll(() => { setupBuildTest(__dirname); }); it('generates .scss -> .css', () => { const css = path.join(cwd, 'css', 'index.css'); expect(fs.existsSync(css)).toBe(true); // file exists expect(fs.readFileSync(css, 'utf-8')).toBeTruthy(); // file has content }); }); ================================================ FILE: test/build/plugin-run-script/public/css/index.css ================================================ body { font-family: "fantasy"; } ================================================ FILE: test/build/plugin-run-script/snowpack.config.js ================================================ module.exports = { mount: { public: '/', }, plugins: [ [ '@snowpack/plugin-run-script', { cmd: 'sass src/css:public/css --no-source-map', }, ], ], }; ================================================ FILE: test/build/plugin-run-script/src/css/index.scss ================================================ $body-font: "fantasy"; body { font-family: $body-font; } ================================================ FILE: test/build/prepare-external-package/package.json ================================================ { "private": true, "version": "1.0.0", "name": "@snowpack/test-prepare-external-package", "description": "Test for packageOptions.external as it applies to prepare.", "scripts": { "testbuild": "snowpack prepare" }, "snowpack": { "mount": { "./src": "/_dist_" }, "packageOptions": { "external": [ "fs", "vue/types" ], "source": "local" } }, "devDependencies": { "snowpack": "^3.8.8" }, "dependencies": { "array-flatten": "^3.0.0" } } ================================================ FILE: test/build/prepare-external-package/prepare-external-package.test.js ================================================ const fs = require('fs'); const path = require('path'); const {setupBuildTest} = require('../../test-utils'); describe('prepare: packageOptions.external', () => { beforeAll(() => { setupBuildTest(__dirname); }); it('prepare external package', () => { expect( fs.existsSync(path.join(__dirname, 'node_modules/.cache/snowpack/build/array-flatten@3.0.0')), ).toEqual(true); expect(fs.existsSync(path.join(__dirname, 'node_modules/.cache/snowpack/build/fs'))).toEqual( false, ); expect( fs.existsSync(path.join(__dirname, 'node_modules/.cache/snowpack/build/vue/types')), ).toEqual(false); }); }); ================================================ FILE: test/build/prepare-external-package/src/index.js ================================================ import 'fs'; import 'array-flatten'; import 'vue/types'; ================================================ FILE: test/build/react-lazy-bundle/package.json ================================================ { "name": "@snowpackjs/test-react-lazy", "description": "Tests React Lazy component resolution", "version": "1.0.0", "scripts": { "testbuild": "snowpack build" }, "dependencies": { "react": "^17.0.2", "react-dom": "^17.0.2", "react-router": "^5.2.0", "react-router-dom": "^5.2.0" }, "devDependencies": { "@snowpack/plugin-react-refresh": "^2.5.0", "@snowpack/plugin-sass": "^1.4.0", "snowpack": "^3.3.2" } } ================================================ FILE: test/build/react-lazy-bundle/public/index.html ================================================ React Lazy Test
================================================ FILE: test/build/react-lazy-bundle/react-lazy.test.js ================================================ const path = require('path'); const {setupBuildTest, readFiles} = require('../../test-utils'); const cwd = path.join(__dirname, 'build'); let files = {}; describe('package: bootstrap', () => { beforeAll(() => { setupBuildTest(__dirname); files = readFiles(cwd); }); it('transforms relative URLs correctly', () => { // The original bug was that when setting bundle: true, URLs were incorrectly transformed. // This tests that `//dist/components//Articles.js` doesn’t happen anymore. expect(files['/dist/index.js']).toEqual( expect.stringContaining(`lazy(()=>import("./components/Articles.js")`), ); }); }); ================================================ FILE: test/build/react-lazy-bundle/snowpack.config.js ================================================ module.exports = { mount: { public: {url: '/', static: true, resolve: false}, src: {url: '/dist'}, }, optimize: { bundle: true, minify: true, sourcemap: true, splitting: true, treeshake: true, target: 'safari11', }, plugins: ['@snowpack/plugin-sass', '@snowpack/plugin-react-refresh'], }; ================================================ FILE: test/build/react-lazy-bundle/src/components/App.jsx ================================================ import React from 'react'; function App({children}) { return
{children}
; } export default App; ================================================ FILE: test/build/react-lazy-bundle/src/components/Articles.jsx ================================================ import React from 'react'; function Articles() { return

Articles

; } export default Articles; ================================================ FILE: test/build/react-lazy-bundle/src/index.jsx ================================================ import React from 'react'; import ReactDOM from 'react-dom'; import {BrowserRouter as Router, Route, Switch} from 'react-router-dom'; import App from './components/App'; const Articles = React.lazy(() => import('./components/Articles')); ReactDOM.render( , document.querySelector('#app'), ); ================================================ FILE: test/build/test-workspace-component/Layout.ts ================================================ export const bob = 42; ================================================ FILE: test/build/test-workspace-component/README.md ================================================ # test-workspace-component This is a test component for the workspace that lets us test symlink support. It is private, not published, and should not ever be needed outside of testing. ================================================ FILE: test/build/test-workspace-component/SvelteComponent.svelte ================================================
Hello! This is a test component!
================================================ FILE: test/build/test-workspace-component/index.mjs ================================================ export * from './Layout' export function testComponent() { return 42; } ================================================ FILE: test/build/test-workspace-component/package.json ================================================ { "name": "test-workspace-component", "private": true, "version": "1.0.0", "license": "MIT", "main": "index.mjs", "dependencies": { "canvas-confetti": "^1.2.0" } } ================================================ FILE: test/build/test-workspace-component/works-without-extension.ts ================================================ export function testFn() { return 42 as number; } ================================================ FILE: test/create-snowpack-app/__snapshots__/create-snowpack-app.test.js.snap ================================================ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`create-snowpack-app app-template-11ty > build: _snowpack/pkg/canvas-confetti.js 1`] = ` "var module = {}; (function main(global, module2, isWorker, workerSize) { var canUseWorker = !!(global.Worker && global.Blob && global.Promise && global.OffscreenCanvas && global.OffscreenCanvasRenderingContext2D && global.HTMLCanvasElement && global.HTMLCanvasElement.prototype.transferControlToOffscreen && global.URL && global.URL.createObjectURL); function noop() { } function promise(func) { var ModulePromise = module2.exports.Promise; var Prom = ModulePromise !== void 0 ? ModulePromise : global.Promise; if (typeof Prom === \\"function\\") { return new Prom(func); } func(noop, noop); return null; } var raf = function() { var TIME = Math.floor(1e3 / 60); var frame, cancel; var frames = {}; var lastFrameTime = 0; if (typeof requestAnimationFrame === \\"function\\" && typeof cancelAnimationFrame === \\"function\\") { frame = function(cb) { var id = Math.random(); frames[id] = requestAnimationFrame(function onFrame(time) { if (lastFrameTime === time || lastFrameTime + TIME - 1 < time) { lastFrameTime = time; delete frames[id]; cb(); } else { frames[id] = requestAnimationFrame(onFrame); } }); return id; }; cancel = function(id) { if (frames[id]) { cancelAnimationFrame(frames[id]); } }; } else { frame = function(cb) { return setTimeout(cb, TIME); }; cancel = function(timer) { return clearTimeout(timer); }; } return {frame, cancel}; }(); var getWorker = function() { var worker; var prom; var resolves = {}; function decorate(worker2) { function execute(options, callback) { worker2.postMessage({options: options || {}, callback}); } worker2.init = function initWorker(canvas) { var offscreen = canvas.transferControlToOffscreen(); worker2.postMessage({canvas: offscreen}, [offscreen]); }; worker2.fire = function fireWorker(options, size, done) { if (prom) { execute(options, null); return prom; } var id = Math.random().toString(36).slice(2); prom = promise(function(resolve) { function workerDone(msg) { if (msg.data.callback !== id) { return; } delete resolves[id]; worker2.removeEventListener(\\"message\\", workerDone); prom = null; done(); resolve(); } worker2.addEventListener(\\"message\\", workerDone); execute(options, id); resolves[id] = workerDone.bind(null, {data: {callback: id}}); }); return prom; }; worker2.reset = function resetWorker() { worker2.postMessage({reset: true}); for (var id in resolves) { resolves[id](); delete resolves[id]; } }; } return function() { if (worker) { return worker; } if (!isWorker && canUseWorker) { var code = [ \\"var CONFETTI, SIZE = {}, module = {};\\", \\"(\\" + main.toString() + \\")(this, module, true, SIZE);\\", \\"onmessage = function(msg) {\\", \\" if (msg.data.options) {\\", \\" CONFETTI(msg.data.options).then(function () {\\", \\" if (msg.data.callback) {\\", \\" postMessage({ callback: msg.data.callback });\\", \\" }\\", \\" });\\", \\" } else if (msg.data.reset) {\\", \\" CONFETTI.reset();\\", \\" } else if (msg.data.resize) {\\", \\" SIZE.width = msg.data.resize.width;\\", \\" SIZE.height = msg.data.resize.height;\\", \\" } else if (msg.data.canvas) {\\", \\" SIZE.width = msg.data.canvas.width;\\", \\" SIZE.height = msg.data.canvas.height;\\", \\" CONFETTI = module.exports.create(msg.data.canvas);\\", \\" }\\", \\"}\\" ].join(\\"\\"); try { worker = new Worker(URL.createObjectURL(new Blob([code]))); } catch (e) { typeof console !== void 0 && typeof console.warn === \\"function\\" ? console.warn(\\"🎊 Could not load worker\\", e) : null; return null; } decorate(worker); } return worker; }; }(); var defaults = { particleCount: 50, angle: 90, spread: 45, startVelocity: 45, decay: 0.9, gravity: 1, drift: 0, ticks: 200, x: 0.5, y: 0.5, shapes: [\\"square\\", \\"circle\\"], zIndex: 100, colors: [ \\"#26ccff\\", \\"#a25afd\\", \\"#ff5e7e\\", \\"#88ff5a\\", \\"#fcff42\\", \\"#ffa62d\\", \\"#ff36ff\\" ], disableForReducedMotion: false, scalar: 1 }; function convert(val, transform) { return transform ? transform(val) : val; } function isOk(val) { return !(val === null || val === void 0); } function prop(options, name, transform) { return convert(options && isOk(options[name]) ? options[name] : defaults[name], transform); } function onlyPositiveInt(number) { return number < 0 ? 0 : Math.floor(number); } function randomInt(min, max) { return Math.floor(Math.random() * (max - min)) + min; } function toDecimal(str) { return parseInt(str, 16); } function colorsToRgb(colors) { return colors.map(hexToRgb); } function hexToRgb(str) { var val = String(str).replace(/[^0-9a-f]/gi, \\"\\"); if (val.length < 6) { val = val[0] + val[0] + val[1] + val[1] + val[2] + val[2]; } return { r: toDecimal(val.substring(0, 2)), g: toDecimal(val.substring(2, 4)), b: toDecimal(val.substring(4, 6)) }; } function getOrigin(options) { var origin = prop(options, \\"origin\\", Object); origin.x = prop(origin, \\"x\\", Number); origin.y = prop(origin, \\"y\\", Number); return origin; } function setCanvasWindowSize(canvas) { canvas.width = document.documentElement.clientWidth; canvas.height = document.documentElement.clientHeight; } function setCanvasRectSize(canvas) { var rect = canvas.getBoundingClientRect(); canvas.width = rect.width; canvas.height = rect.height; } function getCanvas(zIndex) { var canvas = document.createElement(\\"canvas\\"); canvas.style.position = \\"fixed\\"; canvas.style.top = \\"0px\\"; canvas.style.left = \\"0px\\"; canvas.style.pointerEvents = \\"none\\"; canvas.style.zIndex = zIndex; return canvas; } function ellipse(context, x, y, radiusX, radiusY, rotation, startAngle, endAngle, antiClockwise) { context.save(); context.translate(x, y); context.rotate(rotation); context.scale(radiusX, radiusY); context.arc(0, 0, 1, startAngle, endAngle, antiClockwise); context.restore(); } function randomPhysics(opts) { var radAngle = opts.angle * (Math.PI / 180); var radSpread = opts.spread * (Math.PI / 180); return { x: opts.x, y: opts.y, wobble: Math.random() * 10, velocity: opts.startVelocity * 0.5 + Math.random() * opts.startVelocity, angle2D: -radAngle + (0.5 * radSpread - Math.random() * radSpread), tiltAngle: Math.random() * Math.PI, color: opts.color, shape: opts.shape, tick: 0, totalTicks: opts.ticks, decay: opts.decay, drift: opts.drift, random: Math.random() + 5, tiltSin: 0, tiltCos: 0, wobbleX: 0, wobbleY: 0, gravity: opts.gravity * 3, ovalScalar: 0.6, scalar: opts.scalar }; } function updateFetti(context, fetti) { fetti.x += Math.cos(fetti.angle2D) * fetti.velocity + fetti.drift; fetti.y += Math.sin(fetti.angle2D) * fetti.velocity + fetti.gravity; fetti.wobble += 0.1; fetti.velocity *= fetti.decay; fetti.tiltAngle += 0.1; fetti.tiltSin = Math.sin(fetti.tiltAngle); fetti.tiltCos = Math.cos(fetti.tiltAngle); fetti.random = Math.random() + 5; fetti.wobbleX = fetti.x + 10 * fetti.scalar * Math.cos(fetti.wobble); fetti.wobbleY = fetti.y + 10 * fetti.scalar * Math.sin(fetti.wobble); var progress = fetti.tick++ / fetti.totalTicks; var x1 = fetti.x + fetti.random * fetti.tiltCos; var y1 = fetti.y + fetti.random * fetti.tiltSin; var x2 = fetti.wobbleX + fetti.random * fetti.tiltCos; var y2 = fetti.wobbleY + fetti.random * fetti.tiltSin; context.fillStyle = \\"rgba(\\" + fetti.color.r + \\", \\" + fetti.color.g + \\", \\" + fetti.color.b + \\", \\" + (1 - progress) + \\")\\"; context.beginPath(); if (fetti.shape === \\"circle\\") { context.ellipse ? context.ellipse(fetti.x, fetti.y, Math.abs(x2 - x1) * fetti.ovalScalar, Math.abs(y2 - y1) * fetti.ovalScalar, Math.PI / 10 * fetti.wobble, 0, 2 * Math.PI) : ellipse(context, fetti.x, fetti.y, Math.abs(x2 - x1) * fetti.ovalScalar, Math.abs(y2 - y1) * fetti.ovalScalar, Math.PI / 10 * fetti.wobble, 0, 2 * Math.PI); } else { context.moveTo(Math.floor(fetti.x), Math.floor(fetti.y)); context.lineTo(Math.floor(fetti.wobbleX), Math.floor(y1)); context.lineTo(Math.floor(x2), Math.floor(y2)); context.lineTo(Math.floor(x1), Math.floor(fetti.wobbleY)); } context.closePath(); context.fill(); return fetti.tick < fetti.totalTicks; } function animate(canvas, fettis, resizer, size, done) { var animatingFettis = fettis.slice(); var context = canvas.getContext(\\"2d\\"); var animationFrame; var destroy; var prom = promise(function(resolve) { function onDone() { animationFrame = destroy = null; context.clearRect(0, 0, size.width, size.height); done(); resolve(); } function update() { if (isWorker && !(size.width === workerSize.width && size.height === workerSize.height)) { size.width = canvas.width = workerSize.width; size.height = canvas.height = workerSize.height; } if (!size.width && !size.height) { resizer(canvas); size.width = canvas.width; size.height = canvas.height; } context.clearRect(0, 0, size.width, size.height); animatingFettis = animatingFettis.filter(function(fetti) { return updateFetti(context, fetti); }); if (animatingFettis.length) { animationFrame = raf.frame(update); } else { onDone(); } } animationFrame = raf.frame(update); destroy = onDone; }); return { addFettis: function(fettis2) { animatingFettis = animatingFettis.concat(fettis2); return prom; }, canvas, promise: prom, reset: function() { if (animationFrame) { raf.cancel(animationFrame); } if (destroy) { destroy(); } } }; } function confettiCannon(canvas, globalOpts) { var isLibCanvas = !canvas; var allowResize = !!prop(globalOpts || {}, \\"resize\\"); var globalDisableForReducedMotion = prop(globalOpts, \\"disableForReducedMotion\\", Boolean); var shouldUseWorker = canUseWorker && !!prop(globalOpts || {}, \\"useWorker\\"); var worker = shouldUseWorker ? getWorker() : null; var resizer = isLibCanvas ? setCanvasWindowSize : setCanvasRectSize; var initialized = canvas && worker ? !!canvas.__confetti_initialized : false; var preferLessMotion = typeof matchMedia === \\"function\\" && matchMedia(\\"(prefers-reduced-motion)\\").matches; var animationObj; function fireLocal(options, size, done) { var particleCount = prop(options, \\"particleCount\\", onlyPositiveInt); var angle = prop(options, \\"angle\\", Number); var spread = prop(options, \\"spread\\", Number); var startVelocity = prop(options, \\"startVelocity\\", Number); var decay = prop(options, \\"decay\\", Number); var gravity = prop(options, \\"gravity\\", Number); var drift = prop(options, \\"drift\\", Number); var colors = prop(options, \\"colors\\", colorsToRgb); var ticks = prop(options, \\"ticks\\", Number); var shapes = prop(options, \\"shapes\\"); var scalar = prop(options, \\"scalar\\"); var origin = getOrigin(options); var temp = particleCount; var fettis = []; var startX = canvas.width * origin.x; var startY = canvas.height * origin.y; while (temp--) { fettis.push(randomPhysics({ x: startX, y: startY, angle, spread, startVelocity, color: colors[temp % colors.length], shape: shapes[randomInt(0, shapes.length)], ticks, decay, gravity, drift, scalar })); } if (animationObj) { return animationObj.addFettis(fettis); } animationObj = animate(canvas, fettis, resizer, size, done); return animationObj.promise; } function fire(options) { var disableForReducedMotion = globalDisableForReducedMotion || prop(options, \\"disableForReducedMotion\\", Boolean); var zIndex = prop(options, \\"zIndex\\", Number); if (disableForReducedMotion && preferLessMotion) { return promise(function(resolve) { resolve(); }); } if (isLibCanvas && animationObj) { canvas = animationObj.canvas; } else if (isLibCanvas && !canvas) { canvas = getCanvas(zIndex); document.body.appendChild(canvas); } if (allowResize && !initialized) { resizer(canvas); } var size = { width: canvas.width, height: canvas.height }; if (worker && !initialized) { worker.init(canvas); } initialized = true; if (worker) { canvas.__confetti_initialized = true; } function onResize() { if (worker) { var obj = { getBoundingClientRect: function() { if (!isLibCanvas) { return canvas.getBoundingClientRect(); } } }; resizer(obj); worker.postMessage({ resize: { width: obj.width, height: obj.height } }); return; } size.width = size.height = null; } function done() { animationObj = null; if (allowResize) { global.removeEventListener(\\"resize\\", onResize); } if (isLibCanvas && canvas) { document.body.removeChild(canvas); canvas = null; initialized = false; } } if (allowResize) { global.addEventListener(\\"resize\\", onResize, false); } if (worker) { return worker.fire(options, size, done); } return fireLocal(options, size, done); } fire.reset = function() { if (worker) { worker.reset(); } if (animationObj) { animationObj.reset(); } }; return fire; } module2.exports = confettiCannon(null, {useWorker: true, resize: true}); module2.exports.create = confettiCannon; })(function() { if (typeof window !== \\"undefined\\") { return window; } if (typeof self !== \\"undefined\\") { return self; } return this || {}; }(), module, false); var __pika_web_default_export_for_treeshaking__ = module.exports; var create = module.exports.create; export default __pika_web_default_export_for_treeshaking__;" `; exports[`create-snowpack-app app-template-11ty > build: _snowpack/pkg/import-map.json 1`] = ` "{ \\"imports\\": { \\"canvas-confetti\\": \\"./canvas-XXXXXXXX.js\\" } }" `; exports[`create-snowpack-app app-template-11ty > build: about/index.html 1`] = ` " Snowpack App

About

11ty, powered by Snowpack.


Back to Home

" `; exports[`create-snowpack-app app-template-11ty > build: allFiles 1`] = ` Array [ "_snowpack/pkg/canvas-confetti.js", "_snowpack/pkg/import-map.json", "about/index.html", "dist/index.js", "index.html", "static/favicon.png", "static/index.css", "static/logo.svg", ] `; exports[`create-snowpack-app app-template-11ty > build: dist/index.js 1`] = ` "/** * This file is just a silly example to show everything working in the browser. * When you're ready to start on your site, clear the file. Happy hacking! **/ import confetti from '../_snowpack/pkg/canvas-XXXXXXXX.js'; confetti.create(document.getElementById('canvas'), { resize: true, useWorker: true, })({ particleCount: 200, spread: 200 });" `; exports[`create-snowpack-app app-template-11ty > build: index.html 1`] = ` " Snowpack App
About Page " `; exports[`create-snowpack-app app-template-11ty > build: static/index.css 1`] = ` "body { background: #222; color: #eee; font-family: Arial, Helvetica, sans-serif; text-align: center; } a { color: #aaa; } .banner { display: flex; justify-content: center; align-items: center; } .banner img, .banner svg { display: block; padding: 1.5rem; } #canvas { display: block; margin: 0rem auto; width: 720px; height: 420px; }" `; exports[`create-snowpack-app app-template-blank > build: allFiles 1`] = ` Array [ "dist/index.css", "dist/index.js", "favicon.ico", "index.html", "logo.svg", "robots.txt", ] `; exports[`create-snowpack-app app-template-blank > build: dist/index.css 1`] = ` "body { font-size: calc(10px + 2vmin); font-family: Arial, Helvetica, sans-serif; } #img { display: block; margin: auto; height: 128px; width: 128px; padding: 2rem; } p { display: block; margin: 1rem auto; text-align: center; } #counter { background-color: rgb(46, 94, 130); color: white; padding: 4px 8px; border-radius: 4px; } a { color: rgb(46, 94, 130); }" `; exports[`create-snowpack-app app-template-blank > build: dist/index.js 1`] = ` "const counter = document.querySelector('#counter'); let seconds = 0; setInterval(() => { seconds += 1; counter.textContent = seconds; }, 1000);" `; exports[`create-snowpack-app app-template-blank > build: index.html 1`] = ` " Snowpack App

Page has been open for 0 seconds.

Learn web development

" `; exports[`create-snowpack-app app-template-blank-typescript > build: allFiles 1`] = ` Array [ "dist/index.css", "dist/index.js", "favicon.ico", "index.html", "logo.svg", "robots.txt", ] `; exports[`create-snowpack-app app-template-blank-typescript > build: dist/index.css 1`] = ` "body { font-size: calc(10px + 2vmin); font-family: Arial, Helvetica, sans-serif; } #img { display: block; margin: auto; height: 128px; width: 128px; padding: 2rem; } p { display: block; margin: 1rem auto; text-align: center; } #counter { background-color: rgb(46, 94, 130); color: white; padding: 4px 8px; border-radius: 4px; } a { color: rgb(46, 94, 130); }" `; exports[`create-snowpack-app app-template-blank-typescript > build: dist/index.js 1`] = ` "const counter = document.querySelector(\\"#counter\\"); let seconds = 0; setInterval(() => { seconds += 1; counter.textContent = seconds.toString(); }, 1e3); export {};" `; exports[`create-snowpack-app app-template-blank-typescript > build: index.html 1`] = ` " Snowpack App

Page has been open for 0 seconds.

Learn web development

" `; exports[`create-snowpack-app app-template-lit-element > build: _snowpack/pkg/import-map.json 1`] = ` "{ \\"imports\\": { \\"lit-element\\": \\"./lit-element.js\\" } }" `; exports[`create-snowpack-app app-template-lit-element > build: _snowpack/pkg/lit-element.js 1`] = ` "/** * @license * Copyright (c) 2017 The Polymer Project Authors. All rights reserved. * This code may only be used under the BSD style license found at * http://polymer.github.io/LICENSE.txt * The complete set of authors may be found at * http://polymer.github.io/AUTHORS.txt * The complete set of contributors may be found at * http://polymer.github.io/CONTRIBUTORS.txt * Code distributed by Google as part of the polymer project is also * subject to an additional IP rights grant found at * http://polymer.github.io/PATENTS.txt */ /** * True if the custom elements polyfill is in use. */ const isCEPolyfill = typeof window !== 'undefined' && window.customElements != null && window.customElements.polyfillWrapFlushCallback !== undefined; /** * Removes nodes, starting from \`start\` (inclusive) to \`end\` (exclusive), from * \`container\`. */ const removeNodes = (container, start, end = null) => { while (start !== end) { const n = start.nextSibling; container.removeChild(start); start = n; } }; /** * @license * Copyright (c) 2017 The Polymer Project Authors. All rights reserved. * This code may only be used under the BSD style license found at * http://polymer.github.io/LICENSE.txt * The complete set of authors may be found at * http://polymer.github.io/AUTHORS.txt * The complete set of contributors may be found at * http://polymer.github.io/CONTRIBUTORS.txt * Code distributed by Google as part of the polymer project is also * subject to an additional IP rights grant found at * http://polymer.github.io/PATENTS.txt */ /** * An expression marker with embedded unique key to avoid collision with * possible text in templates. */ const marker = \`{{lit-\${String(Math.random()).slice(2)}}}\`; /** * An expression marker used text-positions, multi-binding attributes, and * attributes with markup-like text values. */ const nodeMarker = \`\`; const markerRegex = new RegExp(\`\${marker}|\${nodeMarker}\`); /** * Suffix appended to all bound attribute names. */ const boundAttributeSuffix = '$lit$'; /** * An updatable Template that tracks the location of dynamic parts. */ class Template { constructor(result, element) { this.parts = []; this.element = element; const nodesToRemove = []; const stack = []; // Edge needs all 4 parameters present; IE11 needs 3rd parameter to be null const walker = document.createTreeWalker(element.content, 133 , null, false); // Keeps track of the last index associated with a part. We try to delete // unnecessary nodes, but we never want to associate two different parts // to the same index. They must have a constant node between. let lastPartIndex = 0; let index = -1; let partIndex = 0; const { strings, values: { length } } = result; while (partIndex < length) { const node = walker.nextNode(); if (node === null) { // We've exhausted the content inside a nested template element. // Because we still have parts (the outer for-loop), we know: // - There is a template in the stack // - The walker will find a nextNode outside the template walker.currentNode = stack.pop(); continue; } index++; if (node.nodeType === 1 ) { if (node.hasAttributes()) { const attributes = node.attributes; const { length } = attributes; // Per // https://developer.mozilla.org/en-US/docs/Web/API/NamedNodeMap, // attributes are not guaranteed to be returned in document order. // In particular, Edge/IE can return them out of order, so we cannot // assume a correspondence between part index and attribute index. let count = 0; for (let i = 0; i < length; i++) { if (endsWith(attributes[i].name, boundAttributeSuffix)) { count++; } } while (count-- > 0) { // Get the template literal section leading up to the first // expression in this attribute const stringForPart = strings[partIndex]; // Find the attribute name const name = lastAttributeNameRegex.exec(stringForPart)[2]; // Find the corresponding attribute // All bound attributes have had a suffix added in // TemplateResult#getHTML to opt out of special attribute // handling. To look up the attribute value we also need to add // the suffix. const attributeLookupName = name.toLowerCase() + boundAttributeSuffix; const attributeValue = node.getAttribute(attributeLookupName); node.removeAttribute(attributeLookupName); const statics = attributeValue.split(markerRegex); this.parts.push({ type: 'attribute', index, name, strings: statics }); partIndex += statics.length - 1; } } if (node.tagName === 'TEMPLATE') { stack.push(node); walker.currentNode = node.content; } } else if (node.nodeType === 3 ) { const data = node.data; if (data.indexOf(marker) >= 0) { const parent = node.parentNode; const strings = data.split(markerRegex); const lastIndex = strings.length - 1; // Generate a new text node for each literal section // These nodes are also used as the markers for node parts for (let i = 0; i < lastIndex; i++) { let insert; let s = strings[i]; if (s === '') { insert = createMarker(); } else { const match = lastAttributeNameRegex.exec(s); if (match !== null && endsWith(match[2], boundAttributeSuffix)) { s = s.slice(0, match.index) + match[1] + match[2].slice(0, -boundAttributeSuffix.length) + match[3]; } insert = document.createTextNode(s); } parent.insertBefore(insert, node); this.parts.push({ type: 'node', index: ++index }); } // If there's no text, we must insert a comment to mark our place. // Else, we can trust it will stick around after cloning. if (strings[lastIndex] === '') { parent.insertBefore(createMarker(), node); nodesToRemove.push(node); } else { node.data = strings[lastIndex]; } // We have a part for each match found partIndex += lastIndex; } } else if (node.nodeType === 8 ) { if (node.data === marker) { const parent = node.parentNode; // Add a new marker node to be the startNode of the Part if any of // the following are true: // * We don't have a previousSibling // * The previousSibling is already the start of a previous part if (node.previousSibling === null || index === lastPartIndex) { index++; parent.insertBefore(createMarker(), node); } lastPartIndex = index; this.parts.push({ type: 'node', index }); // If we don't have a nextSibling, keep this node so we have an end. // Else, we can remove it to save future costs. if (node.nextSibling === null) { node.data = ''; } else { nodesToRemove.push(node); index--; } partIndex++; } else { let i = -1; while ((i = node.data.indexOf(marker, i + 1)) !== -1) { // Comment node has a binding marker inside, make an inactive part // The binding won't work, but subsequent bindings will // TODO (justinfagnani): consider whether it's even worth it to // make bindings in comments work this.parts.push({ type: 'node', index: -1 }); partIndex++; } } } } // Remove text binding nodes after the walk to not disturb the TreeWalker for (const n of nodesToRemove) { n.parentNode.removeChild(n); } } } const endsWith = (str, suffix) => { const index = str.length - suffix.length; return index >= 0 && str.slice(index) === suffix; }; const isTemplatePartActive = part => part.index !== -1; // Allows \`document.createComment('')\` to be renamed for a // small manual size-savings. const createMarker = () => document.createComment(''); /** * This regex extracts the attribute name preceding an attribute-position * expression. It does this by matching the syntax allowed for attributes * against the string literal directly preceding the expression, assuming that * the expression is in an attribute-value position. * * See attributes in the HTML spec: * https://www.w3.org/TR/html5/syntax.html#elements-attributes * * \\" \\\\x09\\\\x0a\\\\x0c\\\\x0d\\" are HTML space characters: * https://www.w3.org/TR/html5/infrastructure.html#space-characters * * \\"\\\\0-\\\\x1F\\\\x7F-\\\\x9F\\" are Unicode control characters, which includes every * space character except \\" \\". * * So an attribute is: * * The name: any character except a control character, space character, ('), * (\\"), \\">\\", \\"=\\", or \\"/\\" * * Followed by zero or more space characters * * Followed by \\"=\\" * * Followed by zero or more space characters * * Followed by: * * Any character except space, ('), (\\"), \\"<\\", \\">\\", \\"=\\", (\`), or * * (\\") then any non-(\\"), or * * (') then any non-(') */ const lastAttributeNameRegex = // eslint-disable-next-line no-control-regex /([ \\\\x09\\\\x0a\\\\x0c\\\\x0d])([^\\\\0-\\\\x1F\\\\x7F-\\\\x9F \\"'>=/]+)([ \\\\x09\\\\x0a\\\\x0c\\\\x0d]*=[ \\\\x09\\\\x0a\\\\x0c\\\\x0d]*(?:[^ \\\\x09\\\\x0a\\\\x0c\\\\x0d\\"'\`<>=]*|\\"[^\\"]*|'[^']*))$/; /** * @license * Copyright (c) 2017 The Polymer Project Authors. All rights reserved. * This code may only be used under the BSD style license found at * http://polymer.github.io/LICENSE.txt * The complete set of authors may be found at * http://polymer.github.io/AUTHORS.txt * The complete set of contributors may be found at * http://polymer.github.io/CONTRIBUTORS.txt * Code distributed by Google as part of the polymer project is also * subject to an additional IP rights grant found at * http://polymer.github.io/PATENTS.txt */ const walkerNodeFilter = 133 ; /** * Removes the list of nodes from a Template safely. In addition to removing * nodes from the Template, the Template part indices are updated to match * the mutated Template DOM. * * As the template is walked the removal state is tracked and * part indices are adjusted as needed. * * div * div#1 (remove) <-- start removing (removing node is div#1) * div * div#2 (remove) <-- continue removing (removing node is still div#1) * div * div <-- stop removing since previous sibling is the removing node (div#1, * removed 4 nodes) */ function removeNodesFromTemplate(template, nodesToRemove) { const { element: { content }, parts } = template; const walker = document.createTreeWalker(content, walkerNodeFilter, null, false); let partIndex = nextActiveIndexInTemplateParts(parts); let part = parts[partIndex]; let nodeIndex = -1; let removeCount = 0; const nodesToRemoveInTemplate = []; let currentRemovingNode = null; while (walker.nextNode()) { nodeIndex++; const node = walker.currentNode; // End removal if stepped past the removing node if (node.previousSibling === currentRemovingNode) { currentRemovingNode = null; } // A node to remove was found in the template if (nodesToRemove.has(node)) { nodesToRemoveInTemplate.push(node); // Track node we're removing if (currentRemovingNode === null) { currentRemovingNode = node; } } // When removing, increment count by which to adjust subsequent part indices if (currentRemovingNode !== null) { removeCount++; } while (part !== undefined && part.index === nodeIndex) { // If part is in a removed node deactivate it by setting index to -1 or // adjust the index as needed. part.index = currentRemovingNode !== null ? -1 : part.index - removeCount; // go to the next active part. partIndex = nextActiveIndexInTemplateParts(parts, partIndex); part = parts[partIndex]; } } nodesToRemoveInTemplate.forEach(n => n.parentNode.removeChild(n)); } const countNodes = node => { let count = node.nodeType === 11 ? 0 : 1; const walker = document.createTreeWalker(node, walkerNodeFilter, null, false); while (walker.nextNode()) { count++; } return count; }; const nextActiveIndexInTemplateParts = (parts, startIndex = -1) => { for (let i = startIndex + 1; i < parts.length; i++) { const part = parts[i]; if (isTemplatePartActive(part)) { return i; } } return -1; }; /** * Inserts the given node into the Template, optionally before the given * refNode. In addition to inserting the node into the Template, the Template * part indices are updated to match the mutated Template DOM. */ function insertNodeIntoTemplate(template, node, refNode = null) { const { element: { content }, parts } = template; // If there's no refNode, then put node at end of template. // No part indices need to be shifted in this case. if (refNode === null || refNode === undefined) { content.appendChild(node); return; } const walker = document.createTreeWalker(content, walkerNodeFilter, null, false); let partIndex = nextActiveIndexInTemplateParts(parts); let insertCount = 0; let walkerIndex = -1; while (walker.nextNode()) { walkerIndex++; const walkerNode = walker.currentNode; if (walkerNode === refNode) { insertCount = countNodes(node); refNode.parentNode.insertBefore(node, refNode); } while (partIndex !== -1 && parts[partIndex].index === walkerIndex) { // If we've inserted the node, simply adjust all subsequent parts if (insertCount > 0) { while (partIndex !== -1) { parts[partIndex].index += insertCount; partIndex = nextActiveIndexInTemplateParts(parts, partIndex); } return; } partIndex = nextActiveIndexInTemplateParts(parts, partIndex); } } } /** * @license * Copyright (c) 2017 The Polymer Project Authors. All rights reserved. * This code may only be used under the BSD style license found at * http://polymer.github.io/LICENSE.txt * The complete set of authors may be found at * http://polymer.github.io/AUTHORS.txt * The complete set of contributors may be found at * http://polymer.github.io/CONTRIBUTORS.txt * Code distributed by Google as part of the polymer project is also * subject to an additional IP rights grant found at * http://polymer.github.io/PATENTS.txt */ const directives = new WeakMap(); const isDirective = o => { return typeof o === 'function' && directives.has(o); }; /** * @license * Copyright (c) 2018 The Polymer Project Authors. All rights reserved. * This code may only be used under the BSD style license found at * http://polymer.github.io/LICENSE.txt * The complete set of authors may be found at * http://polymer.github.io/AUTHORS.txt * The complete set of contributors may be found at * http://polymer.github.io/CONTRIBUTORS.txt * Code distributed by Google as part of the polymer project is also * subject to an additional IP rights grant found at * http://polymer.github.io/PATENTS.txt */ /** * A sentinel value that signals that a value was handled by a directive and * should not be written to the DOM. */ const noChange = {}; /** * A sentinel value that signals a NodePart to fully clear its content. */ const nothing = {}; /** * @license * Copyright (c) 2017 The Polymer Project Authors. All rights reserved. * This code may only be used under the BSD style license found at * http://polymer.github.io/LICENSE.txt * The complete set of authors may be found at * http://polymer.github.io/AUTHORS.txt * The complete set of contributors may be found at * http://polymer.github.io/CONTRIBUTORS.txt * Code distributed by Google as part of the polymer project is also * subject to an additional IP rights grant found at * http://polymer.github.io/PATENTS.txt */ /** * An instance of a \`Template\` that can be attached to the DOM and updated * with new values. */ class TemplateInstance { constructor(template, processor, options) { this.__parts = []; this.template = template; this.processor = processor; this.options = options; } update(values) { let i = 0; for (const part of this.__parts) { if (part !== undefined) { part.setValue(values[i]); } i++; } for (const part of this.__parts) { if (part !== undefined) { part.commit(); } } } _clone() { // There are a number of steps in the lifecycle of a template instance's // DOM fragment: // 1. Clone - create the instance fragment // 2. Adopt - adopt into the main document // 3. Process - find part markers and create parts // 4. Upgrade - upgrade custom elements // 5. Update - set node, attribute, property, etc., values // 6. Connect - connect to the document. Optional and outside of this // method. // // We have a few constraints on the ordering of these steps: // * We need to upgrade before updating, so that property values will pass // through any property setters. // * We would like to process before upgrading so that we're sure that the // cloned fragment is inert and not disturbed by self-modifying DOM. // * We want custom elements to upgrade even in disconnected fragments. // // Given these constraints, with full custom elements support we would // prefer the order: Clone, Process, Adopt, Upgrade, Update, Connect // // But Safari does not implement CustomElementRegistry#upgrade, so we // can not implement that order and still have upgrade-before-update and // upgrade disconnected fragments. So we instead sacrifice the // process-before-upgrade constraint, since in Custom Elements v1 elements // must not modify their light DOM in the constructor. We still have issues // when co-existing with CEv0 elements like Polymer 1, and with polyfills // that don't strictly adhere to the no-modification rule because shadow // DOM, which may be created in the constructor, is emulated by being placed // in the light DOM. // // The resulting order is on native is: Clone, Adopt, Upgrade, Process, // Update, Connect. document.importNode() performs Clone, Adopt, and Upgrade // in one step. // // The Custom Elements v1 polyfill supports upgrade(), so the order when // polyfilled is the more ideal: Clone, Process, Adopt, Upgrade, Update, // Connect. const fragment = isCEPolyfill ? this.template.element.content.cloneNode(true) : document.importNode(this.template.element.content, true); const stack = []; const parts = this.template.parts; // Edge needs all 4 parameters present; IE11 needs 3rd parameter to be null const walker = document.createTreeWalker(fragment, 133 , null, false); let partIndex = 0; let nodeIndex = 0; let part; let node = walker.nextNode(); // Loop through all the nodes and parts of a template while (partIndex < parts.length) { part = parts[partIndex]; if (!isTemplatePartActive(part)) { this.__parts.push(undefined); partIndex++; continue; } // Progress the tree walker until we find our next part's node. // Note that multiple parts may share the same node (attribute parts // on a single element), so this loop may not run at all. while (nodeIndex < part.index) { nodeIndex++; if (node.nodeName === 'TEMPLATE') { stack.push(node); walker.currentNode = node.content; } if ((node = walker.nextNode()) === null) { // We've exhausted the content inside a nested template element. // Because we still have parts (the outer for-loop), we know: // - There is a template in the stack // - The walker will find a nextNode outside the template walker.currentNode = stack.pop(); node = walker.nextNode(); } } // We've arrived at our part's node. if (part.type === 'node') { const part = this.processor.handleTextExpression(this.options); part.insertAfterNode(node.previousSibling); this.__parts.push(part); } else { this.__parts.push(...this.processor.handleAttributeExpressions(node, part.name, part.strings, this.options)); } partIndex++; } if (isCEPolyfill) { document.adoptNode(fragment); customElements.upgrade(fragment); } return fragment; } } /** * @license * Copyright (c) 2017 The Polymer Project Authors. All rights reserved. * This code may only be used under the BSD style license found at * http://polymer.github.io/LICENSE.txt * The complete set of authors may be found at * http://polymer.github.io/AUTHORS.txt * The complete set of contributors may be found at * http://polymer.github.io/CONTRIBUTORS.txt * Code distributed by Google as part of the polymer project is also * subject to an additional IP rights grant found at * http://polymer.github.io/PATENTS.txt */ /** * Our TrustedTypePolicy for HTML which is declared using the html template * tag function. * * That HTML is a developer-authored constant, and is parsed with innerHTML * before any untrusted expressions have been mixed in. Therefor it is * considered safe by construction. */ const policy = window.trustedTypes && trustedTypes.createPolicy('lit-html', { createHTML: s => s }); const commentMarker = \` \${marker} \`; /** * The return type of \`html\`, which holds a Template and the values from * interpolated expressions. */ class TemplateResult { constructor(strings, values, type, processor) { this.strings = strings; this.values = values; this.type = type; this.processor = processor; } /** * Returns a string of HTML used to create a \`