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.
<!doctype html> <html> <head> </head> <body> <p>Hello world</p> </body> </html>
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:
<script type="module" src="./index.js"></script>
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:
<script type="importmap"> { "imports": { "superstruct": "https://esm.sh/superstruct@1.0.4" } } </script>
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 will not be 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> | void][]} */ const tests = []; /** * * @param {string} description * @param {() => Promise<void> | 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.
The above is the detailed content of Web Development Without (Build) Tooling. For more information, please follow other related articles on the PHP Chinese website!

Hot AI Tools

Undresser.AI Undress
AI-powered app for creating realistic nude photos

AI Clothes Remover
Online AI tool for removing clothes from photos.

Undress AI Tool
Undress images for free

Clothoff.io
AI clothes remover

Video Face Swap
Swap faces in any video effortlessly with our completely free AI face swap tool!

Hot Article

Hot Tools

Notepad++7.3.1
Easy-to-use and free code editor

SublimeText3 Chinese version
Chinese version, very easy to use

Zend Studio 13.0.1
Powerful PHP integrated development environment

Dreamweaver CS6
Visual web development tools

SublimeText3 Mac version
God-level code editing software (SublimeText3)

Hot Topics











Python is more suitable for beginners, with a smooth learning curve and concise syntax; JavaScript is suitable for front-end development, with a steep learning curve and flexible syntax. 1. Python syntax is intuitive and suitable for data science and back-end development. 2. JavaScript is flexible and widely used in front-end and server-side programming.

The shift from C/C to JavaScript requires adapting to dynamic typing, garbage collection and asynchronous programming. 1) C/C is a statically typed language that requires manual memory management, while JavaScript is dynamically typed and garbage collection is automatically processed. 2) C/C needs to be compiled into machine code, while JavaScript is an interpreted language. 3) JavaScript introduces concepts such as closures, prototype chains and Promise, which enhances flexibility and asynchronous programming capabilities.

The main uses of JavaScript in web development include client interaction, form verification and asynchronous communication. 1) Dynamic content update and user interaction through DOM operations; 2) Client verification is carried out before the user submits data to improve the user experience; 3) Refreshless communication with the server is achieved through AJAX technology.

JavaScript's application in the real world includes front-end and back-end development. 1) Display front-end applications by building a TODO list application, involving DOM operations and event processing. 2) Build RESTfulAPI through Node.js and Express to demonstrate back-end applications.

Understanding how JavaScript engine works internally is important to developers because it helps write more efficient code and understand performance bottlenecks and optimization strategies. 1) The engine's workflow includes three stages: parsing, compiling and execution; 2) During the execution process, the engine will perform dynamic optimization, such as inline cache and hidden classes; 3) Best practices include avoiding global variables, optimizing loops, using const and lets, and avoiding excessive use of closures.

Python and JavaScript have their own advantages and disadvantages in terms of community, libraries and resources. 1) The Python community is friendly and suitable for beginners, but the front-end development resources are not as rich as JavaScript. 2) Python is powerful in data science and machine learning libraries, while JavaScript is better in front-end development libraries and frameworks. 3) Both have rich learning resources, but Python is suitable for starting with official documents, while JavaScript is better with MDNWebDocs. The choice should be based on project needs and personal interests.

Both Python and JavaScript's choices in development environments are important. 1) Python's development environment includes PyCharm, JupyterNotebook and Anaconda, which are suitable for data science and rapid prototyping. 2) The development environment of JavaScript includes Node.js, VSCode and Webpack, which are suitable for front-end and back-end development. Choosing the right tools according to project needs can improve development efficiency and project success rate.

C and C play a vital role in the JavaScript engine, mainly used to implement interpreters and JIT compilers. 1) C is used to parse JavaScript source code and generate an abstract syntax tree. 2) C is responsible for generating and executing bytecode. 3) C implements the JIT compiler, optimizes and compiles hot-spot code at runtime, and significantly improves the execution efficiency of JavaScript.
