Web Development Without (Build) Tooling

WBOY
發布: 2024-08-05 19:11:22
原創
1014 人瀏覽過

Web Development Without (Build) Tooling

When starting a new web project where JavaScript will be used, often the first thing we do is set up build and developer tooling. For example, Vite, which is a popular these days. You might not be aware that complicated build tooling it not needed for all JavaScript (web) projects. In fact, it is now easier to go without than ever before as I will show in this article.

Create a new project with an index.html file.

   

Hello world

登入後複製

If you are using VS Code, install the Live Preview extension. Run it. This is a simple file server with live reload. You can use any file server, Python comes with one built in:

python3 -m http.server
登入後複製

I like Live Preview, because it automatically refreshes the page after making changes to a file.

You should now be able to access your index.html file from the browser and see "Hello world".

Next, create an index.js file:

console.log("Hello world"); export {};
登入後複製

Include it in your index.html:

登入後複製

Open the developer console in your browser. If you see "Hello world", you know that it is loading properly.

Browsers support ECMAScript modules now. You can import other files for their side effects:

import "./some-other-script.js";
登入後複製

or for their exports

import { add, multiply } "./my-math-lib.js";
登入後複製

Pretty cool right? Refer to the MDN guide above for more information.

Packages

You probably don't want to re-invent the wheel, so your project will probably use some third-party packages. That doesn't mean you now need to start using a package manager.

Say we want to use superstruct for data validation. We can can not just load modules from our own (local) file server, but from any URL. esm.sh conveniently provides modules for almost all packages available on npm.

When you visit https://esm.sh/superstruct you can see that you are re-directed to the latest version. You can include this package as follows in your code:

import { assert } from "https://esm.sh/superstruct";
登入後複製

If you want to be on the safe side, you can pin versions.

Types

I don't know about you, but TypeScript spoiled me (and made me lazy). Writing plain JavaScript without help from the type checker feels like writing over a tightrope. Luckily, we don't have to forgo type checking either.

It is time to bust out npm (even though we won't ship any code it provides).

npm init --yes npm install typescript
登入後複製

You can use the TypeScript compiler on JavaScript code just fine! There is first-class support for it. Create a jsconfig.json:

{ "compilerOptions": { "strict": true, "checkJs": true, "allowJs": true, "noImplicitAny": true, "lib": ["ES2022", "DOM"], "module": "ES2022", "target": "ES2022" }, "include": ["**/*.js"], "exclude": ["node_modules"] }
登入後複製

Now run

npm run tsc --watch -p jsconfig.json
登入後複製

and make a type error in your code. The TypeScript compiler should complain:

/** @type {number} **/ const num = "hello";
登入後複製

By the way, the comment you see above is JSDoc. You can annotate your JavaScript with types this way. While it is a little bit more verbose than using TypeScript, and you get used to it pretty quickly. It is also very powerful, as long as you are not writing crazy types (which you should not for most projects) you should be fine.

If you do need a complicated type (helper), you can always add some TypeScript in a .d.ts file.

Is JSDoc just a stepping stone for people stuck with large JavaScript projects to be able to gradually migrate gradually to TypeScript? I don't think so! The TypeScript team also continues to add great features to JSDoc + TypeScript, such as in the upcoming TypeScript release. Auto-completion also works great in VS Code.

Import maps

We learned how to add external packages to our project without a build tool. However, if you split your code in a lot of modules, writing out the complete URL over and over again might be a bit verbose.

We can add an import map to the head section of our index.html:

登入後複製

Now we can simply import this package with

import {} from "superstruct"
登入後複製

Like a 'normal' project. Another benefit is that completion and recognition of types will work as expected if you install the package locally.

npm install --save-dev superstruct
登入後複製

Note that the version in your node_modules directory willnotbe used. You can remove it, and your project will continue to run.

A trick I like to use is to add:

"cdn/": "https://esm.sh/",
登入後複製

To my import map. Then any project available through esm.sh can be used by simply importing it. E.g.:

import Peer from "cdn/peerjs";
登入後複製

If you want to pull types from node_modules for development for this type of import as well, you need to add the following to the compilerOptions of your jsconfig.json:

"paths": { "cdn/*": ["./node_modules/*", "./node_modules/@types/*"] },
登入後複製

Deployment

To deploy your project, copy all the files to a static file host and you are done! If you have ever worked on a legacy JavaScript project, you know the pain of getting build tooling updated that is not even 1-2 years old. Your will not suffer the same fate with this project setup.

Testing

If your JavaScript does not depend on browser APIs, you could just use the test runner that comes bundled with Node.js. But why not write your own test runner that runs right in the browser?

/** @type {[string, () => Promise | void][]} */ const tests = []; /** * * @param {string} description * @param {() => Promise | void} testFunc */ export async function test(description, testFunc) { tests.push([description, testFunc]); } export async function runAllTests() { const main = document.querySelector("main"); if (!(main instanceof HTMLElement)) throw new Error(); main.innerHTML = ""; for (const [description, testFunc] of tests) { const newSpan = document.createElement("p"); try { await testFunc(); newSpan.textContent = `✅ ${description}`; } catch (err) { const errorMessage = err instanceof Error && err.message ? ` - ${err.message}` : ""; newSpan.textContent = `❌ ${description}${errorMessage}`; } main.appendChild(newSpan); } } /** * @param {any} val */ export function assert(val, message = "") { if (!val) throw new Error(message); }
登入後複製

Now create a file example.test.js.

import { test, assert } from "@/test.js"; test("1+1", () => { assert(1 + 1 === 2); });
登入後複製

And a file where you import all your tests:

import "./example.test.js"; console.log("This should only show up when running tests");
登入後複製

Run this on page load:

await import("@/test/index.js"); // file that imports all tests (await import("@/test.js")).runAllTests();
登入後複製

And you got a perfect TDD setup. To run only a section of the tests you can comment out a few .test.js import, but test execution speed should only start to become a problem when you have accumulated a lot of tests.

Benefits

Why would you do this? Well, using fewer layers of abstraction makes your project easier to debug. There is also the credo to "use the platform". The skills you learn will transfer better to other projects. Another advantage is, when you return to a project built like this in 10 years, it will still just work and you don't need to do archeology to try to revive a build tool that has been defunct for 8 years. An experience many web developers that worked on legacy projects will be familiar with.

See plainvanillaweb.com for some more ideas.

以上是Web Development Without (Build) Tooling的詳細內容。更多資訊請關注PHP中文網其他相關文章!

來源:dev.to
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板
關於我們 免責聲明 Sitemap
PHP中文網:公益線上PHP培訓,幫助PHP學習者快速成長!