You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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 API
  • app文件夹下的代码是前端代码,最终要在浏览器里跑,浏览器没有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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.20 11:57:24