programing

번들에 node_modules 종속성이 있는 프로덕션용 NestJS 앱을 올바르게 구축하는 방법은 무엇입니까?

javajsp 2023. 6. 8. 19:23

번들에 node_modules 종속성이 있는 프로덕션용 NestJS 앱을 올바르게 구축하는 방법은 무엇입니까?

끝나고nest build또는nest build --webpackdist 폴더에 필요한 모든 모듈이 포함되어 있지 않습니다.Error: Cannot find module '@nestjs/core'뛰려고 할 때node main.js.

https://docs.nestjs.com/ 에서 운영을 위한 앱을 올바르게 구축하는 방법에 대한 명확한 지침을 찾을 수 없었습니다. 그래서 제가 무언가를 놓친 것이 아닐까요?

기본적으로, nest CLI는 다음을 포함하여 지원하지 않습니다.node_modules에 대한 의존성dist보따리를 묶다


그러나 번들의 종속성을 포함하는 사용자 지정 웹 팩 구성의 몇 가지 커뮤니티 예(예: 번들-네스트)가 있습니다.이번 호에서 설명한 바와 같이, 다음을 포함하는 것이 필요합니다.webpack.IgnorePlugin사용하지 않는 동적 라이브러리를 화이트리스트에 추가합니다.

ncc는 완전한 NestJs 앱을 하나의 js 파일로 번들링하는 데 큰 역할을 합니다.

ncc build src/main.ts --out dist/main.js

더 이상의 구성은 필요하지 않습니다. 일부는 수정해야 할 수도 있습니다.import경로. 트리 쉐이킹을 수행하고 바인딩을 감지하여 별도의 폴더에 복사하기도 합니다.

bundle-nest 보관/재보관 완료:

NestJS 또는 실제로는 일반적으로 NodeJS 웹 서버를 번들로 제공하지 않는 것이 좋습니다.이는 커뮤니티가 트리 셰이크 및 NestJS 앱 번들을 시도하는 동안 과거 참조용으로 보관됩니다.자세한 내용은 @kamilmysliwiec 주석을 참조하십시오.

사용 중인 라이브러리에 따라 Node.js 애플리케이션(NestJS 애플리케이션뿐만 아니라)을 모든 종속성(node_modules 폴더에 위치한 외부 패키지)과 함께 번들하지 않는 경우가 많습니다.이렇게 하면 (트리 쉐이킹으로 인해) 도커 이미지가 작아지고, 메모리 소비가 다소 감소하고, 부트스트랩 시간이 약간 증가할 수 있지만(특히 서버가 없는 환경에서 유용함), 에코시스템에서 일반적으로 사용되는 많은 일반적인 라이브러리와 함께 작동하지 않습니다.예를 들어 MongoDB로 NestJS(또는 그냥 express) 응용 프로그램을 구축하려고 하면 콘솔에 다음 오류가 표시됩니다.

오류: webpack EmptyContext에서 '//drivers/node-mongodb-native/connection' 모듈을 찾을 수 없습니다.

왜죠? 몽구스는 케르베로스(C++)와 노드집프에 의존하는 몽구스에 의존하기 때문입니다.

음에 대해서, 에서대해.mongo일부 예외를 만들 수 있습니다(일부 모듈은 에 남깁니다).node_modules), 할 수 있습니까?모든 것이 아니면 아무것도 아닌 것도 아닙니다.하지만 여전히, 저는 당신이 이 길을 따르고 있는지 확신할 수 없습니다.나는 방금 a를 묶는 것에 성공했습니다.nestjs어플.개념 증명이었는데, 생산에 들어갈지는 잘 모르겠습니다.그리고 힘들었습니다. 제가 그 과정에서 무언가를 부러뜨렸을 수도 있지만, 언뜻 보기에는 효과가 있습니다.가장 복잡한 부분은.정말 그랬어요.rollup그리고.babel부양 가족으로서그리고 앱 코드에서는 어떤 이유로 무조건 호출합니다(운영 중인 UDP noop).어쨌든 이 경로를 따르려면 패키지의 코드를 디버깅/검사할 준비가 되어 있어야 합니다.또한 새 패키지가 프로젝트에 추가될 때 해결 방법을 추가해야 할 수도 있습니다.하지만 모든 것은 당신의 의존도에 달려 있습니다. 저의 경우보다 더 쉬울 수도 있습니다.새로 만든 + 앱의 경우 비교적 간단했습니다.

마지막으로 사용한 구성(기본값보다 우선함):

webpack.config.js(webpack-5.58.2,@nestjs/cli-8.1.4):

const path = require('path');
const MakeOptionalPlugin = require('./make-optional-plugin');
module.exports = (defaultOptions, webpack) => {
    return {
        externals: {},  // make it not exclude `node_modules`
                        // https://github.com/nestjs/nest-cli/blob/v7.0.1/lib/compiler/defaults/webpack-defaults.ts#L24
        resolve: {
            ...defaultOptions.resolve,
            extensions: [...defaultOptions.resolve.extensions, '.json'], // some packages require json files
                                                                         // https://unpkg.com/browse/babel-plugin-polyfill-corejs3@0.4.0/core-js-compat/data.js
                                                                         // https://unpkg.com/browse/core-js-compat@3.19.1/data.json
            alias: {
                // an issue with rollup plugins
                // https://github.com/webpack/enhanced-resolve/issues/319
                '@rollup/plugin-json': '/app/node_modules/@rollup/plugin-json/dist/index.js',
                '@rollup/plugin-replace': '/app/node_modules/@rollup/plugin-replace/dist/rollup-plugin-replace.cjs.js',
                '@rollup/plugin-commonjs': '/app/node_modules/@rollup/plugin-commonjs/dist/index.js',
            },
        },
        module: {
            ...defaultOptions.module,
            rules: [
                ...defaultOptions.module.rules,

                // a context dependency
                // https://github.com/RobinBuschmann/sequelize-typescript/blob/v2.1.1/src/sequelize/sequelize/sequelize-service.ts#L51
                {test: path.resolve('node_modules/sequelize-typescript/dist/sequelize/sequelize/sequelize-service.js'),
                use: [
                    {loader: path.resolve('rewrite-require-loader.js'),
                    options: {
                        search: 'fullPath',
                        context: {
                            directory: path.resolve('src'),
                            useSubdirectories: true,
                            regExp: '/\\.entity\\.ts$/',
                            transform: ".replace('/app/src', '.').replace(/$/, '.ts')",
                        },
                    }},
                ]},

                // adminjs resolves some files using stack (relative to the requiring module)
                // and actually it needs them in the filesystem at runtime
                // so you need to leave node_modules/@adminjs/upload
                // I failed to find a workaround
                // it bundles them to `$prj_root/.adminjs` using `rollup`, probably on production too
                // https://github.com/SoftwareBrothers/adminjs-upload/blob/v2.0.1/src/features/upload-file/upload-file.feature.ts#L92-L100
                {test: path.resolve('node_modules/@adminjs/upload/build/features/upload-file/upload-file.feature.js'),
                use: [
                    {loader: path.resolve('rewrite-code-loader.js'),
                    options: {
                        replacements: [
                            {search: /adminjs_1\.default\.bundle\('\.\.\/\.\.\/\.\.\/src\/features\/upload-file\/components\/edit'\)/,
                            replace: "adminjs_1.default.bundle('/app/node_modules/@adminjs/upload/src/features/upload-file/components/edit')"},

                            {search: /adminjs_1\.default\.bundle\('\.\.\/\.\.\/\.\.\/src\/features\/upload-file\/components\/list'\)/,
                            replace: "adminjs_1.default.bundle('/app/node_modules/@adminjs/upload/src/features/upload-file/components/list')"},

                            {search: /adminjs_1\.default\.bundle\('\.\.\/\.\.\/\.\.\/src\/features\/upload-file\/components\/show'\)/,
                            replace: "adminjs_1.default.bundle('/app/node_modules/@adminjs/upload/src/features/upload-file/components/show')"},
                        ],
                    }},
                ]},

                // not sure what babel does here
                // I made it return standardizedName
                // https://github.com/babel/babel/blob/v7.16.4/packages/babel-core/src/config/files/plugins.ts#L100
                {test: path.resolve('node_modules/@babel/core/lib/config/files/plugins.js'),
                use: [
                    {loader: path.resolve('rewrite-code-loader.js'),
                    options: {
                        replacements: [
                            {search: /const standardizedName = [^;]+;/,
                            replace: match => `${match} return standardizedName;`},
                        ],
                    }},
                ]},

                // a context dependency
                // https://github.com/babel/babel/blob/v7.16.4/packages/babel-core/src/config/files/module-types.ts#L51
                {test: path.resolve('node_modules/@babel/core/lib/config/files/module-types.js'),
                use: [
                    {loader: path.resolve('rewrite-require-loader.js'),
                    options: {
                        search: 'filepath',
                        context: {
                            directory: path.resolve('node_modules/@babel'),
                            useSubdirectories: true,
                            regExp: '/(preset-env\\/lib\\/index\\.js|preset-react\\/lib\\/index\\.js|preset-typescript\\/lib\\/index\\.js)$/',
                            transform: ".replace('./node_modules/@babel', '.')",
                        },
                    }},
                ]},
            ],
        },
        plugins: [
            ...defaultOptions.plugins,
            // some optional dependencies, like this:
            // https://github.com/nestjs/nest/blob/master/packages/core/nest-application.ts#L45-L52
            // `webpack` detects optional dependencies when they are in try/catch
            // https://github.com/webpack/webpack/blob/main/lib/dependencies/CommonJsImportsParserPlugin.js#L152
            new MakeOptionalPlugin([
                '@nestjs/websockets/socket-module',
                '@nestjs/microservices/microservices-module',
                'class-transformer/storage',
                'fastify-swagger',
                'pg-native',
            ]),
        ],

        // to have have module names in the bundle, not some numbers
        // although numbers are sometimes useful
        // not really needed
        optimization: {
            moduleIds: 'named',
        }
    };
};

make-optional-plugin.js:

class MakeOptionalPlugin {
    constructor(deps) {
        this.deps = deps;
    }

    apply(compiler) {
        compiler.hooks.compilation.tap('HelloCompilationPlugin', compilation => {
            compilation.hooks.succeedModule.tap(
                'MakeOptionalPlugin', (module) => {
                    module.dependencies.forEach(d => {
                        this.deps.forEach(d2 => {
                            if (d.request == d2)
                                d.optional = true;
                        });
                    });
                }
            );
        });
    }
}

module.exports = MakeOptionalPlugin;

rewrite-require-loader.js:

// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#escaping
function escapeRegExp(string) {
  return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
}

function processFile(source, search, replace) {
    const re = `require\\(${escapeRegExp(search)}\\)`;
    return source.replace(
        new RegExp(re, 'g'),
        `require(${replace})`);
}

function processFileContext(source, search, context) {
    const re = `require\\(${escapeRegExp(search)}\\)`;
    const _d = JSON.stringify(context.directory);
    const _us = JSON.stringify(context.useSubdirectories);
    const _re = context.regExp;
    const _t = context.transform || '';
    const r = source.replace(
        new RegExp(re, 'g'),
        match => `require.context(${_d}, ${_us}, ${_re})(${search}${_t})`);
    return r;
}

module.exports = function(source) {
    const options = this.getOptions();
    return options.context
        ? processFileContext(source, options.search, options.context)
        : processFile(source, options.search, options.replace);
};

rewrite-code-loader.js:

function processFile(source, search, replace) {
    return source.replace(search, replace);
}

module.exports = function(source) {
    const options = this.getOptions();
    return options.replacements.reduce(
        (prv, cur) => {
            return prv.replace(cur.search, cur.replace);
        },
        source);
};

앱을 구축하는 방법은 다음과 같습니다.

$ nest build --webpack

나는 소스 지도를 신경쓰지 않았다, 왜냐하면 그 목표는.nodejs.

복사 붙여넣기만 하면 되는 구성이 아닙니다. 프로젝트에 필요한 내용을 직접 파악해야 합니다.

여기서 한 가지 더 요령이 있습니다만, 아마 필요 없을 것입니다.

업데이 adminjs사전 구축된 번들과 함께 제공되므로 이 구성이 훨씬 단순할 수 있습니다.

언급URL : https://stackoverflow.com/questions/59635276/how-to-correctly-build-nestjs-app-for-production-with-node-modules-dependencies