The browser is your runtime.
The shell is your task runner.
NOT FOR FRAMEWORKS.
This is an experiment in defining a minimalistic but full-featured environment for web development where the primary runtime for the project is the browser itself, not node or deno.
Deno is used here to implement some dev tasks but only in a capacity that's similar to how bash or external command line tools work. You don't need to have deno installed to use this, the setup script will download a local copy that'll be used in just this project.
Instead of a task runner this project uses the file system and your shell. Tasks
are in the tasks/
directory and are all executable scripts. Edit the scripts
to configure them (although this is rarely necessary).
Running tasks/setup
will download a pinned version of deno
into .bin
. This
is the version that the task scripts will use. There is also a
.vscode/settings.json
file that tells your deno extension in VS Code to use
the local binary.
This setup doesn't interfere with your shell's $PATH
in any way.
The version is pinned to v1.36.4
. This can be changed by editing the
DENO_VERSION
variable and the DENO_SHA
checksum variable towards the top of
the tasks/setup
script.
The deno.jsonc
configuration file is set up so that deno knows that js
files
in this project are primarily for the browser. It is also documented with
comments. It will use the provided import_map.json
file as the project's
primary import map
The setup task will also generate a self-signed certificate that the serve
task will then use to provide an https
server.
Tests are based on mocha.
Running tasks/serve
will start up a simple https
web server that hosts the
current directory and an HTML page that runs all test files found in tests/
and automatically includes the import_map.json
file as an import map.
This server is set up to deliver files with all the necessary headers to enable
cross-site isolation. This means that features
such as SharedArrayBuffer
, high resolution timers, and Atomics.wait()
. As a
result cross-origin resources are likely to break. At some point I'll update
this to make this configurable so you can turn isolation off if necessary.
This page will automatically refresh any time something in the current working directory is changed.
Running tasks/chrome
or tasks/firefox
will attempt to launch browser
instances with fresh profiles and, if necessary, exceptions for self-signed
https certificates.
For coverage, use the coverage panel in Chrome's development tools.
To configure the test file (i.e. change mocha's interface or timeout settings),
edit the configuration variables in the tasks/serve
script itself.
The .test
directory contains vendored copies of mocha
and chai
to simplify
test setup.
I've also included an example simpletest.js
file that shows how mocha
and
chai
are set up by default.
The tasks/vendor
script will save local copies of your project and create an
import map that the test server and the bundler will load automatically.
That means your project will no longer be fetching those dependencies over the network.
Re-run tasks/vendor
whenever you modify the import_map.json
file in the
project root or whenever you add or remove an http
dependency in your code.
Running tasks/lint
will run deno's linter on the current directory with the
current settings from deno.jsonc
. The deno VS Code extension should also
automatically lint files in your workspace.
Running tasks/format
will run deno's formatter on the current directory with
the current settings from deno.jsonc
. The deno VS Code extension should also
automatically format files in your workspace.
Type checking JS files is disabled by default. You can enable this throughout
the workspaces by setting checkJS
to true
in deno.jsonc
or in individual
files by including // @ts-check
in the first line of the file.
Running tasks/check
will run deno's typescript checker on all files in the
src
directory. You can configure this in the tasks/check
file.
Running tasks/bundle
will bundle every .js
file in the root of the project
into dist
using esbuild. It additionally will fetch modules over the network
(or using deno's cache) just like the browser.
This means you don't need to install packages locally in any way. Modules work
just like they do in the browser and the bundler will use the
vendor/import_map.json
file if it exist to find modules, which means that
dependencies that have been vendored with tasks/vendor
should be automatically
supported.
Code-splitting is enabled so imports that are shared by the root entry points
will be imported as shared chunks into the dist/
directory.
tasks/add
, tasks/outdated
, and tasks/audit
are a first pass at a bare
mininum dependency-management system using import maps.
tasks/add npm:hash-wasm
will add a npm:hash-wasm
entry to your import map
imports
that maps to the latest esm.sh
url for the hash-wasm
package. You
can use semantic versioning so tasks/add npm:hash-wasm@\^4.9 --exact
will
create that entry directly without resolving it.
tasks/outdated
will resolve the url for the listed entry in the import map and
the latest url for that package and compare the version in the two urls.
Packages whose resolved mapped version is lower than the resolved latest version
are listed.
tasks/audit
will load the dependency graph, using the graph tools made by the
deno
team, extract package names and versions from esm.sh
urls that appear
in the graph, submit those to
Sonatype's OSSIndex and alert you if any
of the packages you list in your import map are importing what looks like a
vulnerable dependency.
(My original plan was to use Github's REST API for global security advisories, but I couldn't get it to work with insecure scoped packages. They would always return as if they had no vulnerabilities.)
This setup includes continuous integration. Whenever you push to GitHub it will run an Action that installs Playwright, runs the Mocha tests in Firefox, WebKit, and Chrome and passes or fails depending on the results of the Mocha unit tests.
These tests only run in the action and you don't need to have node or a node version manager to use this setup.
The GitHub Action workflow does this by running tasks/ci
,
npx playwright install --with-deps
, and tasks/ci-test
.
If you already have node setup on your machine, you could use Playwright as a
test runner for mocha with coverage. Just run tasks/ci
to move the config
files to project root, commit, and finally run
npx playwright install --with-deps
.
then you can use tasks/ci-test
to run the tests with coverage.
This is error-prone if your platform isn't well-supported by the Playwright
team. You may have to edit playwright.config.js
and remove the webkit
project for it to run.
This task will copy the package.json
, package-lock.json
and
playwright.config.js
files from ci/
into the project root that are necessary
for Playwright tests to run.
You can edit the original files in ci/
for additional configuration.
Run this task after tasks/ci
to have Playwright run all of the tests files in
ci/tests
. You can include additional integration tests by adding Playwright
test files to ci/tests
. This task will automatically gather coverage
information from Playwright and use the local copy of deno to both report
coverage data to standard out (should be visible in Action logs) and to
cov_profile.lcov
which can be sent to coverage reporting services by adding
their actions to the workflow.
To automatically gather coverage data from additional integration tests, you
need to import the pageWithCoverage
fixture from ci/pageWithCoverage.js
and
use that as the main page
object for interacting with the page. See
ci/tests/mocha-spec.js
as an example.
This only works if you use URL module specifiers. Bare imports breaks default compatibility.
We don't need no stinking package registry.
If you're using browser-compatible JS modules that fetch over HTTP, you don't need to publish your project on npm to distribute your module.
Since a Raggedy Dev project is set up to use browser compatible importing, any content delivery network that lets you fetch files from a GitHub repository will serve to let you import the project.
For this reason, build files in dist
are included in the commit.
tasks/bundle
now creates two bundles for each entry point:
.min.js: a minified bundle of the entry point and all of its dependencies .js (no .min): a bundle of local JS files only, all dependencies are external.
This should mean that, for example, this should work for importing one of the main entry points for this sample project:
import * as raggedy from "https://esm.sh/gh/baldurbjarnason/raggedy-dev-setup@v1.1.0/dist/raggedy.js";
With that you would get the raggedy.js
entry point in the release tagged
v1.1.0
.
If raggedy.js
imports a dependency from esm.sh
that's shared with something
else you're importing, then we won't be loading thtat twice any more.
However, if you aren't importing the module to use in your own JS code, but are
instead adding a script to a web page, then the .min.js
files would be a
better fit as they are bundled to be standalone and minified.
<script type="module" src="https://esm.sh/gh/baldurbjarnason/raggedy-dev-setup@v1.1.0/dist/raggedy.min.js"></script>
This setup includes workspace settings to enable the deno extension (if installed) and set the editor to use it to format on save for the file types that deno supports.
This setup includes project settings to enable the deno extension (if installed) and set the editor to use it to format on save for the file types that deno supports.
It also disables LSP-typescript
in the project as that seems to conflict with
LSP-deno
.