从零搭建一个无构建工具的现代前端项目?我试了

阿乐
阿乐 管理员 黑卡会员
发布于 2026-09-06 13:52 ·1 浏览 ·0 回复

“现在的项目还搞什么构建?”我时常在社区看到类似论调。毕竟,Node_modules 动辄几个G,Vite 启动虽快但对老旧笔记本依然是一种折磨。所以当朋友问我,能不能不装 Node.js,不用 React 脚手架,就靠浏览器原生能力,打造一个“现代化”的前端项目时,我决定亲自试一试。

我把这次实验的目标定得很明确:**没有 Webpack、Vite,甚至不用 npm install,抛弃 JSX 与 SFC,完全依靠浏览器原生 ESM(模块加载)机制——用 Vue 3 的运行时版本,配合浏览器原生的导入映射表(Import Maps)来实现。**

结果如何?我确实成功了,而且整个过程出乎意料地顺畅。这也让我对这种反主流的开发方式,有了全新的认知。

抛开构建,首先要适应“裸写”逻辑

第一步自然是搭建页面结构。以往我们用 Vue 脚手架时,习惯性地将页面拆分成 `.vue` 单文件组件。但在无构建环境中,一切都得回归本源。

由于没有编译步骤,我们便无法直接书写 `<template>` 语法块。我选择的是vue.global.js。它并不像其它代码那样依赖模块打包器,只需要在 HTML 中引入 CDN 链接,就能直接通过 `Vue.createApp` 挂载。

为了让工程结构清晰,我采用了原生的 JavaScript 模块来替代“单文件组件”:

// components/Counter.js
export default {
  template: `
    <div>
      <p>当前计数:{{ count }}</p>
      <button @click="increment">戳我</button>
    </div>
  `,
  data() {
    return { count: 0 };
  },
  methods: {
    increment() { this.count++; },
  }
};

当我看到这段代码时,最直观的感受是:在没有语法糖的加持下,代码依然极其凝练。 那个曾经让我们又爱又恨的 虚拟DOM 和响应式系统,从未像此刻一样显得如此“退居幕后”。你不再需要担心 `ref` 或 `reactive` 的自动解包问题,因为一切都在纯 JS 的掌控中。

关键先生:Import Maps 的妙用

在所有实验步骤中,最让我感到惊艳的,是 Import Maps 这个现代浏览器特性。以前我们使用 npm 包时,`import { createRouter } from 'vue-router'` 是理所当然的事。但在浏览器中,由于没有 `node_modules` 目录,浏览器并不知道 `'vue-router'` 这种裸模块路径指向哪里。

如果你的 index.html 里写了 `import xxx from 'vue'`,控制台只会给你弹一个大大的红叉 Invalid module specifier。这时候,`importmap` 便成了救世主。

我在 HTML 的 `<head>` 中加入了这么一段配置:

<script type="importmap">
{
  "imports": {
    "vue": "https://unpkg.com/vue@3/dist/vue.runtime.esm-browser.prod.js",
    "vue-router": "https://unpkg.com/vue-router@4/dist/vue-router.esm-browser.js",
    "pinia": "https://unpkg.com/pinia@2/dist/pinia.esm-browser.js"
  }
}
</script>

这个映射表,就像一个“翻译官”。当你请求 `'vue'` 时,浏览器会自动去请求这个完整的 CDN 地址。用 Vue 全家桶做项目?没问题,你只需要将它们全部列在 `imports` 中即可。

借助这一特性,整个项目的依赖变得一目了然,不需要任何打包器来解析依赖树,加载逻辑被完全透明化地交还给了浏览器。这感觉棒极了,就像回到了几年前,却又带着现代响应式框架的优雅。

踩坑记录:开发体验的双刃剑

如果只谈优点,那就有些过于 “软文” 了。实际动手后,我确实遇到了几个令人抓狂的瞬间。

最折磨人的是 没有模块热替换。因为文件都是直接通过 http 地址加载的,浏览器一旦发起请求,就会缓存该模块。这意味着,我每次写完代码保存,如果不强制刷新页面(Cmd+Shift+R),根本看不到更新效果。

此外,模板编译时的警告排查很痛苦。因为是用运行时版本,我不能再使用 `Vue.component` 那样极其冗长的写法,而是要非常注意模板里的指令写法。甚至当我在模板里写了一个不存在的数据变量时,反馈并不像 Vite 那样直接报错并指出哪一行,而是悄无声息地在控制台打出一条措辞隐晦的 warn,需要靠 Vue Devtools 去细致排查。

还有一点值得注意:在无构建模式下,原生 TypeScript 支持依旧很弱(虽然浏览器 108 版本后已能剥离类型,但工程化应用仍显不足),这逼得我在写纯函数时也不得不放弃强类型,或是采用 JSDoc 注释来稍作弥补。对于习惯了 TS 约束的开发者,这种“降级感”需要一定心理准备。

重新审视“不构建”的意义

一番折腾下来,项目在我的浏览器中流畅跑了起来。尽管它的模块加载方式是简陋的(在大型项目中,几百个 HTTP 请求会拖垮加载速度),但它的确证明了:**只要我们不需要复杂的 JSX 转换、不需要极致的首屏体积优化,我们完全可以重拾一种更轻量且随性的前端开发方式。**

这种模式特别适合以下场景:没有 Node 环境的服务器上做后台管理页面、快速原型 demo、或是作为公司内部某个小工具的独立页面。当别人还在等待 npm install 去安装一个 300MB 的 TypeScript 编译器时,你已经通过 CDN 写完了一个带路由和状态管理的页面。这种“无依赖一身轻”的快乐,也许正是对抗繁重工程化的一剂良药。

但如果你问我最喜欢的是哪一点,我会说:这套 DIY 的流程,让我重新看清了 Vue 核心 API 的用法。 在剥去脚手架的重重糖衣之后,那些 API 的本质才清晰地浮现出来。放下对构建的依赖,尝试回归浏览器原生特性,是一场非常有趣的解构之旅。

全部回复 0

还没有回复,来抢沙发~