webpack 5 打包 nodejs 后端

1,147 阅读2分钟

装webpack

npm i -D webpack webpack-cli @babel/cli @babel/core babel-loader babel-plugin-transform-runtime babel-preset-env copy-webpack-plugin terser-webpack-plugin

太长不看版:

const path = require('path');
const fs = require('fs');
const webpack = require('webpack');
const TerserPlugin = require("terser-webpack-plugin");
const CopyPlugin = require("copy-webpack-plugin");

const PATHS = {
    src: path.resolve(__dirname, 'src'),
    release: path.resolve(__dirname, 'dist')
}

const nodeModules = {};
fs.readdirSync('node_modules')
    .filter((nModule) => !['.bin'].includes(nModule))
    .forEach((nModule) => {
        nodeModules[nModule] = nModule;
    });

module.exports = {
    mode: 'production',
    entry: path.resolve(__dirname, 'app.js'),
    output: {
        filename: 'bundle.js',
        path: PATHS.release,
        library: {
            type: 'commonjs'
        }
    },
    target: 'node',
    externals: nodeModules, 
    context: __dirname,
    node: {
        __filename: false,
        __dirname: false
    },
    module: {
        rules: [
            {
                test: /\.js$/,
                exclude: [/node_modules/, ]
                loader: 'babel-loader',
                options: {
                    presets: [
                        [
                            'env', {
                                'targets': {
                                    node: '6.10'
                                }
                            }
                        ]
                    ],
                },
            }
        ],

    },
    plugins: [
        new webpack.DefinePlugin({
            'process.env.NODE_ENV': '"production"',
            'process.env.PORT': '8080',
        }),
        new CopyPlugin({
            patterns: [
              { from: path.resolve(__dirname, 'install.js'), to: path.resolve(PATHS.release, 'install.js') },
              { from: path.resolve(__dirname, 'uninstall.js'), to: path.resolve(PATHS.release, 'uninstall.js') },
              { from: path.resolve(__dirname, 'package.json'), to: path.resolve(PATHS.release, 'package.json') },
            ],
          }),
    ],
    optimization: {
        minimize: true,
        minimizer: [new TerserPlugin({
            test: /\.js(\?.*)?$/i,
        })],
    },
    resolve: {
        extensions: ['.js', '.json']
    }
}              

-------- 正文分割线 --------

首先分析你的后端代码,

不需要打包: node_modules

需要打包: src/.js, src/.json, package.json, install.js(把nodejs后端注册成service), uninstall.js(从service里remove掉我的nodejs后端)

module.exports = {
    mode: 'production',  // 后端的运行环境: production, development, none, webpack对每个环境都有对应的处理逻辑,具体看官网: https://www.webpackjs.com/concepts/#mode 
    entry: path.resolve(__dirname, 'app.js'), //入口. webpack打包出来的bundle.js实际上是一个立即执行程序,entry是告诉webpack从哪里开始构建. 官网介绍:https://www.webpackjs.com/concepts/#entry
    output: {  
        filename: 'bundle.js',  // 把你的后端打包成一个叫bundle.js的文件
        path: PATHS.release,  // bundle.js 应该放在哪里
        // libraryTarget: 'commonjs' //我的后端是js. 官网说以后libraryTarget会改成library.type: https://www.webpackjs.com/configuration/output/#outputlibrarytarget   
        library: {
            type: 'commonjs'  
        }
    },
    target: 'node', // 我这是nodejs 后端,所以我选了node,前端用的react18,框架自带webpack.config里, target选的是browserslist,具体选项参看官网https://www.webpackjs.com/configuration/target/
    externals: nodeModules, //像是const axios = require('axios)这种语句,默认webpack会把node_modules/axios打包进来,我们不想让bundle.js太大,就得告诉webpack别打包,等runtime的时候再去引用. 如果你引用别的项目的可执行文件,你的externals就不止node_modules下面的东西了. 官网介绍: https://www.webpackjs.com/configuration/externals/ 
    context: __dirname, // 入口文件的folder的绝对路径,其实我不太理解为啥给了entry还得给context,不过官网说是就是吧,https://www.webpackjs.com/configuration/entry-context/ 
    node: {  // 设置为true: bundle.js里暴露你真实的文件名和路径 https://webpack.docschina.org/configuration/node/
        __filename: false,
        __dirname: false
    },
    module: {
        rules: [
            {
                test: /\.js$/,
                exclude: [/node_modules/, ]
                loader: 'babel-loader',
                options: {
                    presets: [
                        [
                            'env', {
                                'targets': {
                                    node: '6.10'
                                }
                            }
                        ]
                    ],
                },
            }
        ],

    },
    plugins: [
        new webpack.DefinePlugin({  // 定义全局变量 https://webpack.js.org/plugins/define-plugin/
            'process.env.NODE_ENV': '"production"',
            'process.env.PORT': '8080',
        }),
        new CopyPlugin({ // 把不需要打包,需要单独存放的文件copy去dist文件夹里. 我这里copy的是把后端注册成service的install.js, 把后端从service remove掉的uninstall.js, 还有package.json, 因为对install.js的调用写在package.json里面, 官网:https://webpack.js.org/plugins/copy-webpack-plugin/
            patterns: [
              { from: path.resolve(__dirname, 'install.js'), to: path.resolve(PATHS.release, 'install.js') },
              { from: path.resolve(__dirname, 'uninstall.js'), to: path.resolve(PATHS.release, 'uninstall.js') },
              { from: path.resolve(__dirname, 'package.json'), to: path.resolve(PATHS.release, 'package.json') },
            ],
          }),
    ],
    optimization: { // webpack会根据mode来优化bundle, 不过你也可以自己通过optimizaion来设置, https://webpack.docschina.org/configuration/optimization/
        minimize: true, // 用minimizer来压缩bundle
        minimizer: [new TerserPlugin({
            test: /\.js(\?.*)?$/i,
        })],
    },
    resolve: { // https://webpack.docschina.org/configuration/resolve
        extensions: ['.js', '.json']
    }
}              

自此, webpack.config.js配完了, 正常来说, 你在package.json添加一项

"scripts": {
    ...,
    "build": "webpack --config webpack.config.js"
}

然后npm run build 就完事了. 但是我得配jekins, 所以我得在js里面webpack --config webpack.config.js, 等webpack打包完, 还得压缩成zip, 然后上传服务器, 所以继续改代码:

const build = () => {
    const argv = require('yargs').argv; 
    const env = argv?.['env'] || 'PRD'; // 从外部接参 node deploy.js --env=PRD 
    
    const webpackConfig = env === 'DEV' ? 'webpack.dev.config.js' : 'webpack.config.js';
    
    // 跑"webpack --config webpack.config.js"
    const cmd = util.promisify(child_process.exec);
    (async () => {
        await (cmd(`webpack --config ${webpackConfig}`));
    })();
    
    // 下面比较猥琐, 用setInterval + fs.lstatSync(bundlePath, {throwIfNoEntry: false})?.isFile() 来判断bundle.js究竟生成了没有. 具体代码涉密就不提供了
}

const compress = () => {
    // 用了archiver 包, 具体代码涉密也不写了, 官网写的也蛮清楚的了, 自取吧
    // https://www.npmjs.com/package/archiver
}

(async () => {
    await build();
    await compress();
    await upload();
})()

上传到server之后, 用node-windows发布到service里面; 或者用pm2编译成exe, 运行exe, 可以在task manager里面找到.