Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ES module option. #316

Open
wants to merge 14 commits into
base: master
Choose a base branch
from
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ This generator can also be further configured with the following command line fl
--no-view use static html instead of view engine
-c, --css <engine> add stylesheet <engine> support (less|stylus|compass|sass) (defaults to plain css)
--git add .gitignore
--es6 generate ES6 code and module-type project (requires Node 14.x or higher)
-f, --force force on non-empty directory
-h, --help output usage information

Expand Down
23 changes: 16 additions & 7 deletions bin/express-cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ var MODE_0666 = parseInt('0666', 8)
var MODE_0755 = parseInt('0755', 8)
var TEMPLATE_DIR = path.join(__dirname, '..', 'templates')
var VERSION = require('../package').version
var MIN_ES6_VERSION = 14

// parse args
var unknown = []
Expand All @@ -26,7 +27,7 @@ var args = parseArgs(process.argv.slice(2), {
H: 'hogan',
v: 'view'
},
boolean: ['ejs', 'force', 'git', 'hbs', 'help', 'hogan', 'pug', 'version'],
boolean: ['ejs', 'es6', 'force', 'git', 'hbs', 'help', 'hogan', 'pug', 'version'],
default: { css: true, view: true },
string: ['css', 'view'],
unknown: function (s) {
Expand Down Expand Up @@ -93,9 +94,10 @@ function createApplication (name, dir, options, done) {
var pkg = {
name: name,
version: '0.0.0',
type: options.es6 ? 'module' : 'commonjs',

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have you considered using a conditional and adding the type property instead of defaulting to commonjs ? The current code doesn't set a type property and defaulting to commonjs has a potential to change behavior for existing code.

  var pkg = {
    name: name,
    version: '0.0.0',
    private: true,
    scripts: {
      start: options.esm ? 'node ./bin/www.js' : 'node ./bin/www'
    },
    dependencies: {
      debug: '~2.6.9',
      express: '~4.17.1'
    }
  }
  if (options.esm) {
    pkg.type = 'module'
  }

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I considered not setting type: commonjs explicitly. I'm almost positive that this will not change existing code. For one thing, express-generator is generating new code ;-) For another, even if someone dropped existing code underneath the generated code, the behavior of the existing code should not change since commonjs is the default for type. All this line does is make this default explicit and "future-proof" the package against possible later changes to the default. That's a good thing, I think.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see your point about commonjs being the default module loading style for Node. I’m just going to call this out as a risky change because it’s technically impacting existing functionality and can be written in a way to avoid that risk. Ultimately, only time can tell what the positive move is.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✔️ Fair enough.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

node isn’t going to be able to change the default; it’d break the world.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wouldn't be too sure of that. After all, many folks thought that Y2K bugs might literally break the world, but they didn't.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LOL. I heard someone say that Y2K bugs didn't break the world because of all the planning and work to prepare for it.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

True enough. But in the case of changing the type default, the work for an individual package is adding one line to package.json if it's not already there. One could even imagine automated fixes for this change as opposed to bringing COBOL programmers out of retirement ;-)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All that said, I've made the change you recommended, @joeyguerra. The type field is now only generated when the --es6 switch is active.

private: true,
scripts: {
start: 'node ./bin/www'
start: options.es6 ? 'node ./bin/www.mjs' : 'node ./bin/www'
},
dependencies: {
debug: '~2.6.9',
Expand All @@ -104,8 +106,8 @@ function createApplication (name, dir, options, done) {
}

// JavaScript
var app = loadTemplate('js/app.js')
var www = loadTemplate('js/www')
var app = loadTemplate(options.es6 ? 'mjs/app.js' : 'js/app.js')
var www = loadTemplate(options.es6 ? 'mjs/www' : 'js/www')

// App name
www.locals.name = name
Expand Down Expand Up @@ -160,7 +162,9 @@ function createApplication (name, dir, options, done) {

// copy route templates
mkdir(dir, 'routes')
copyTemplateMulti('js/routes', dir + '/routes', '*.js')
copyTemplateMulti(
options.es6 ? 'mjs/routes' : 'js/routes',
dir + '/routes', options.es6 ? '*.mjs' : '*.js')

if (options.view) {
// Copy view templates
Expand Down Expand Up @@ -283,10 +287,10 @@ function createApplication (name, dir, options, done) {
pkg.dependencies = sortedObject(pkg.dependencies)

// write files
write(path.join(dir, 'app.js'), app.render())
write(path.join(dir, options.es6 ? 'app.mjs' : 'app.js'), app.render())
write(path.join(dir, 'package.json'), JSON.stringify(pkg, null, 2) + '\n')
mkdir(dir, 'bin')
write(path.join(dir, 'bin/www'), www.render(), MODE_0755)
write(path.join(dir, options.es6 ? 'bin/www.mjs' : 'bin/www'), www.render(), MODE_0755)

var prompt = launchedFromCmd() ? '>' : '$'

Expand Down Expand Up @@ -433,6 +437,10 @@ function main (options, done) {
usage()
error('option `-v, --view <engine>\' argument missing')
done(1)
} else if (options.es6 && process.versions.node.split('.')[0] < MIN_ES6_VERSION) {
usage()
error('option `--es6\' requires Node version ' + MIN_ES6_VERSION + '.x or higher')
done(1)
} else {
// Path
var destinationPath = options._[0] || '.'
Expand Down Expand Up @@ -521,6 +529,7 @@ function usage () {
console.log(' --no-view use static html instead of view engine')
console.log(' -c, --css <engine> add stylesheet <engine> support (less|stylus|compass|sass) (defaults to plain css)')
console.log(' --git add .gitignore')
console.log(' --es6 generate ES6 code and module-type project (requires Node 14.x or higher)')
console.log(' -f, --force force on non-empty directory')
console.log(' --version output the version number')
console.log(' -h, --help output usage information')
Expand Down
54 changes: 54 additions & 0 deletions templates/mjs/app.js.ejs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<% if (view) { -%>
import createError from 'http-errors';
<% } -%>
import express from 'express';
drjeffjackson marked this conversation as resolved.
Show resolved Hide resolved
import path from 'node:path';
import { fileURLToPath } from 'node:url';
<% Object.keys(modules).sort().forEach(function (variable) { -%>
import <%- variable %> from '<%- modules[variable] %>';
<% }); -%>

<% Object.keys(localModules).sort().forEach(function (variable) { -%>
import <%- variable %> from '<%- localModules[variable] %>.mjs';
<% }); -%>

const __dirname = path.dirname(fileURLToPath(import.meta.url));

const app = express();

<% if (view) { -%>
// view engine setup
<% if (view.render) { -%>
app.engine('<%- view.engine %>', <%- view.render %>);
<% } -%>
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', '<%- view.engine %>');

<% } -%>
<% uses.forEach(function (use) { -%>
app.use(<%- use %>);
<% }); -%>

<% mounts.forEach(function (mount) { -%>
app.use(<%= mount.path %>, <%- mount.code %>);
<% }); -%>

<% if (view) { -%>
// catch 404 and forward to error handler
app.use((req, res, next) => {
next(createError(404));
});

// error handler
app.use((err, req, res, next) => {
// set locals, only providing error in development
res.locals.message = err.message;
res.locals.error = req.app.get('env') === 'development' ? err : {};

// render the error page
res.status(err.status || 500);
res.render('error');
});

<% } -%>
export default app;
9 changes: 9 additions & 0 deletions templates/mjs/routes/index.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import express from 'express';
const router = express.Router();

/* GET home page. */
router.get('/', (req, res, next) => {
res.render('index', { title: 'Express' });
});

export default router;
9 changes: 9 additions & 0 deletions templates/mjs/routes/users.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import express from 'express';
const router = express.Router();

/* GET users listing. */
router.get('/', (req, res, next) => {
res.send('respond with a resource');
});

export default router;
91 changes: 91 additions & 0 deletions templates/mjs/www.ejs
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
#!/usr/bin/env node

/**
* Module dependencies.
*/

import app from '../app.mjs';
import http from 'node:http';
import debugFunction from 'debug';
const debug = debugFunction('<%- name %>:server');

/**
* Get port from environment and store in Express.
*/

const port = normalizePort(process.env.PORT || '3000');
app.set('port', port);

/**
* Create HTTP server.
*/

const server = http.createServer(app);

/**
* Listen on provided port, on all network interfaces.
*/

server.listen(port);
server.on('error', onError);
server.on('listening', onListening);

/**
* Normalize a port into a number, string, or false.
*/

function normalizePort(val) {
const port = parseInt(val, 10);

if (isNaN(port)) {
// named pipe
return val;
}

if (port >= 0) {
// port number
return port;
}

return false;
}

/**
* Event listener for HTTP server "error" event.
*/

function onError(error) {
if (error.syscall !== 'listen') {
throw error;
}

const bind = typeof port === 'string'
? 'Pipe ' + port
: 'Port ' + port;

// handle specific listen errors with friendly messages
switch (error.code) {
case 'EACCES':
console.error(bind + ' requires elevated privileges');
process.exit(1);
break;
case 'EADDRINUSE':
console.error(bind + ' is already in use');
process.exit(1);
break;
default:
throw error;
}
}

/**
* Event listener for HTTP server "listening" event.
*/

function onListening() {
const addr = server.address();
const bind = typeof addr === 'string'
? 'pipe ' + addr
: 'port ' + addr.port;
debug('Listening on ' + bind);
}
Loading
Loading