Node+Express+EJS项目脚本文件夹配置及import问题求助
问题描述
本人是SSR新手,正在用Node、Express和EJS搭建个人作品集,遇到以下问题:
- 在
app/index.js里尝试用import导入其他类、管理主App类,把这个文件引入home.ejs时,出现"you cannot use import outside a module"错误 - 给script标签加上
<script type="module" src="...mypath"></script>后,import的问题解决了,但没法用Node原生API(比如export default class Component extends EventEmitter),推测这些API只能在服务端运行,求指正 - 尝试在
package.json里加"type:module"导致整个应用崩溃,想知道能不能仅通过config.js配置,让app文件夹下的文件像config.js一样用import,是否必须用Webpack
附上相关代码和文件结构:
config.js代码
import express from "express"; import path from "path"; import logger from "morgan"; import bodyParser from "body-parser"; import routes from "./routes"; import sassMiddleware from "node-sass-middleware"; import EventEmitter from 'events' import i18next from "i18next"; import i18nextBackend from "i18next-fs-backend"; import i18nextMiddleware from "i18next-http-middleware"; const { PORT = 5050 } = process.env; const app = express(); i18next.use(i18nextBackend) .use(i18nextMiddleware.LanguageDetector) .init({ debug: true, fallbackLng: 'en', preload: ['de', 'en'], backend: { loadPath: './locales/{{lng}}/translation.json' }, detection: { order: ['querystring', 'cookie'], caches: ['cookie'], lookupQuerystring: 'lang', lookupCookie: 'lang', ignoreCase: true, cookieSecure: false }, }) app.use(i18nextMiddleware.handle(i18next)); app.set("views", path.join(__dirname, "../views")); app.set("app", path.join(__dirname, "../app")); app.set("view engine", "ejs"); app.use(logger("dev")); app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: false })); app.locals = { config: { whatever: 'this is' } }; app.use( sassMiddleware({ src: path.join(__dirname, "../public/scss"), dest: path.join(__dirname, "../public"), indentedSyntax: false, sourceMap: false }) ); app.use(express.static(path.join(__dirname, "../public"))); app.use("/", routes); app.use(function (req, res) { res.status(404).render('404.ejs'); }); app.use(function (req, res) { res.status(500).render('500.ejs'); }); app.use(EventEmitter) app.listen(PORT, () => { console.log(`Listening on port ${PORT}`); }); export default app;
package.json代码
{ "name": "My-portfolio", "version": "0.0.0", "private": true, "author": "My portfolio", "main": "dist/config.js", "scripts": { "start": "node dist/config.js", "dev": "nodemon src/config.js --exec \"node -r dotenv/config -r @babel/register\"", "clean": "rimraf dist", "build": "npm run clean && mkdir -p dist && babel src -s -D -d dist", "postinstall": "npm run build" }, "dependencies": { "@babel/cli": "^7.18.9", "@babel/core": "^7.18.9", "@babel/plugin-proposal-class-properties": "^7.18.6", "@babel/plugin-proposal-object-rest-spread": "^7.18.9", "@babel/preset-env": "^7.18.9", "body-parser": "^1.19.0", "ejs": "^3.1.8", "express": "^4.18.1", "graphql": "^16.5.0", "graphql-request": "^4.3.0", "i18next": "^21.9.1", "i18next-browser-languagedetector": "^6.1.5", "i18next-fs-backend": "^1.1.5", "i18next-http-middleware": "^3.2.1", "locomotive-scroll": "^4.1.4", "lodash": "^4.17.21", "morgan": "^1.10.0", "node-sass-middleware": "^1.0.1", "rimraf": "^3.0.0" }, "devDependencies": { "@babel/register": "^7.18.9", "dotenv": "^16.0.1", "nodemon": "^2.0.19" }, "babel": { "presets": [ [ "@babel/preset-env", { "targets": { "node": "current" } } ] ], "plugins": [ "@babel/plugin-proposal-class-properties", "@babel/plugin-proposal-object-rest-spread" ] } }
文件结构

问题解答
1. 先理清服务端和前端代码的边界
你猜的没错,Node原生API(比如EventEmitter)只能在Node.js环境(服务端)运行,浏览器根本不认识这些模块:
src文件夹下的代码(比如config.js)是服务端代码,靠Babel转译后在Node环境执行,所以能正常使用import和Node APIapp文件夹下的代码是前端代码,最终要在浏览器里跑,浏览器没有events模块,直接用extends EventEmitter肯定报错
2. 为什么加"type:module"会导致应用崩溃
你的项目现在用Babel把ES模块语法转成Node默认支持的CommonJS模块。一旦在package.json加了"type:module",Node会把所有.js文件都当成ES模块处理,但Babel编译后的代码可能还是CommonJS语法(比如module.exports),或者模块导入导出规则冲突(比如ES模块要求导入路径加.js,而你之前的代码没加),直接导致整个服务启动失败。
3. 如何让app文件夹的前端代码使用import
有两种可行方案,按需选择:
方案一:用Webpack/Vite等打包工具(推荐)
这是前端模块化开发的标准流程:
- 把
app文件夹作为前端源码入口,用打包工具把所有ES模块打包成浏览器能识别的单文件(或多文件) - 打包工具还能帮你处理依赖、转译ES6+语法到兼容旧浏览器、压缩代码等
- 打包后的文件放到
public文件夹,在EJS里引入打包后的脚本即可,不用加type="module"(也可以配置成输出ES模块)
方案二:纯浏览器原生ES模块(不用打包工具)
如果项目小,不想折腾打包工具,可以这么做:
- 所有前端脚本都用
<script type="module">引入 - 前端代码里不能用Node API,比如
EventEmitter可以换成浏览器原生的EventTarget,或者用第三方轻量事件库 - 导入路径必须写完整后缀(比如
import Component from './Component.js',不能只写./Component)
4. 能不能只让app文件夹用import,服务端保持原样
完全可以,两者本来就是独立的环境:
- 服务端代码继续靠Babel转译ES模块,不用改
package.json的type字段 - 前端代码要么用浏览器原生ES模块,要么用打包工具处理,和服务端的模块系统互不影响
- 不需要通过
config.js做特殊配置,只要把app文件夹作为静态资源让Express托管(你已经通过express.static托管了public,可以把app也托管,或者把前端代码放到public下)
内容的提问来源于stack exchange,提问作者druj_nasu
相关产品推荐
相关产品推荐

