先写一个简单的demo
const Koa = require('koa');
const app = new Koa();
const Router = require('@koa/router');
const router = new Router();
router.get('/', (ctx) => {
ctx.body = 'ok';
console.log('get');
});
const port = process.env.PORT || 4000;
app.use(router.routes()).use(router.allowedMethods);
app.listen(port, () => {
console.log('监听4000端口');
});
启动项目,可以执行node src/app.js,打印出 '监听4000端口'。但其实我们在写代码的时候希望代码能够重新启动并编译,开发环境我们用nodemon, 执行nodemon src/app.js,修改代码之后可以看到重新启动了。
不过我们通常会比较熟悉esm的模块规范,所以将上面的代码的require改为熟悉的import语法
import Koa from 'koa';
const app = new Koa();
import Router from '@koa/router';
const router = new Router();
router.get('/', (ctx) => {
ctx.body = 'ok';
console.log('get');
});
const port = process.env.PORT || 4000;
app.use(router.routes()).use(router.allowedMethods);
app.listen(port, () => {
console.log('监听4000端口');
});
这时如果直接执行nodemon src/app.js会报错,Cannot use import statement outside a module,因为node默认是commonjs规范,几种解决办法:
- 把
package.json中的type设置为module, 这样NodeJs会认为所有的文件都是esm模块了,不过其他的一些设置文件我们是没必要去写成import语法的,commonJs规范在构建处理文件时更高效 - 将
.js后缀改为.mjs,nodeJs就知道要以esm规范去处理了 - 使用babel-node对代码进行实时转译(推荐)
这里要用到babel-node的原因是,我们写了es6+的语法,但是nodeJs默认是cmj规范,所以我们要对语法以及模块规范进行转换,这时就要用到babel了,babel会将es6+语法转为目标环境支持的语法(浏览器/node)。babel是基于插件和预设工作的,我们先引入@babel/preset-env包,设置target为node
{
"presets": [
[
"@babel/preset-env",
{
"targets": {
"node": "current"
}
}
]
]
}
意思是基于这些预设,帮我转换为node中的模块规范。
执行npx babel-node src/app.js, 正常打印,但是可能大家会发现,在执行完命令终端那里会闪一下,因为babel-node是即时编译的,性能较差,所以我们也只是在开发环境用,不会在生产环境用。在生产环境打包编译好,再部署。同样的,继续用nodemon来监听文件变化,在package.json中我们这样配置:npx nodemon --exec babel-node src/app.js,意思是 通过babel-node运行入口文件,并监听文件变化。
以上是本地启动项目,对项目构建时,我们要区分一下环境,开发环境和生产环境,它们还有一些公用的配置,所以新建一个config文件夹,一共有三个文件,webpack.config.dev.js,webpack.config.prod.js,webpack.config.base.js,
配置分别如下:
// webpack.config.base.js
const path = require('path');
const webpack = require('webpack');
const nodeExternals = require('webpack-node-externals');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const Dotenv = require('dotenv-webpack');
const dotenv = require('dotenv');
// 加载 .env 文件
dotenv.config();
const webpackConfig = {
// 这里是告诉webpack目标环境是NodeJs, 主要在于模块解析规则的不同。比如不加的话,模块解析是esm规范,如果是node, 解析是commonjs规范。对于nodeJs而言,解析到require('path')的时候,webpack知道这是一个nodejs内置模块,否则会报错,因为在整个项目中都找不到path.
target: 'node',
entry: {
// 设置入口文件
server: path.join(__dirname, '../src/app.js'),
},
output: {
// 设置打包后的文件和位置
filename: '[name].bundle.js',
path: path.join(__dirname, '../dist'),
},
module: {
rules: [
{
test: /\.js|jsx$/,
// 这里可以用swc-loader代替babel-loader加快打包速度
use: {
loader: 'swc-loader',
},
// 尽量将 loader 应用于最少数量的必要模块,因此设置include
// 只针对该目录下的js文件进行处理
// include: path.join(__dirname, '../src'),
},
],
},
resolve: {
// 模块解析目录,默认是node_modules,
modules: ['node_modules', path.resolve('src')],
// 在导入的时候通常是不加文件后缀的,在打包的时候webpack自动添加这些后缀进行匹配
extensions: ['.js', '.json'],
alias: {
// 设置别名指向对应目录
'@': path.join(__dirname, '../src'),
},
},
externals: [nodeExternals()], // 排除对node_modules里的依赖进行打包
plugins: [
new Dotenv({
// 根据不同的环境加载环境变量
path: path.join(__dirname, `../.env.${process.env.NODE_ENV}`),
}),
new CleanWebpackPlugin(), // 打包前清除dist目录
],
};
module.exports = webpackConfig;
// .swcrc
{
"module": {
"type": "es6",
"ignoreDynamic": true
},
"jsc": {
"parser": {
"syntax": "typescript",
"dynamicImport": true,
"decorators": true,
"tsx": true
},
"transform": {
"legacyDecorator": true,
"decoratorMetadata": true,
"react": {
"runtime": "automatic",
"throwIfNamespace": true,
"useBuiltins": true
}
},
"target": "esnext",
"loose": true,
"externalHelpers": true,
"keepClassNames": false
}
}
// webpack开发配置
const { merge } = require('webpack-merge');
const baseConfig = require('./webpack.config.base');
// 通过webpack-merge合并基础配置,添加开发时配置
const webpackConfig = merge(baseConfig, {
mode: 'development', // 开发模式
devtool: 'eval-source-map', // 开发时出错能知道在源代码中哪一行
stats: {
children: false, // webpack打包时子模块信息设置不显示
modules: false, // 不显示模块信息
},
});
module.exports = webpackConfig;
// webpack.config.prod.js
const { merge } = require('webpack-merge');
const baseConfig = require('./webpack.config.base');
const TerserPlugin = require('terser-webpack-plugin');
// 通过webpack-merge合并基础配置,添加生产时配置
const webpackConfig = merge(baseConfig, {
mode: 'production', // 生产模式下,webpack自己也有开启一些优化,开发模式同理
// 输出的信息的内容显示
stats: {
children: false, // webpack打包时子模块信息设置不显示
warnings: false, // 警告不显示
},
optimization: {
minimizer: [
// terser-webpack-plugin插件可以压缩js代码
new TerserPlugin(),
],
},
});
module.exports = webpackConfig;
补充说一下Dotenv-webpack踩的坑,用的时候忘看官网了
As such, it does a text replace in the resulting bundle for any instances of 'process.env'
它是会替换打包好的文件中的process.env为.env文件中的变量,直接访问是访问不到的。如果要想打印的话,可以用Dotenv.
const dotenv = require('dotenv');
// 加载 .env 文件
dotenv.config();
这样就能打印了。
以上webpack webpack-cli最新版本可用