less / less.js

Less. The dynamic stylesheet language.
http://lesscss.org
Apache License 2.0
17k stars 3.41k forks source link

Out of memory when trying to bundle AdminLTE and Bootstrap #3627

Open brainz80 opened 3 years ago

brainz80 commented 3 years ago

I'm trying to bundle admin-lte@2.4.18 and bootstrap@3.4.1 with less@4.1.1 via webpack@5.39.0.

My webpack.config.js looks like this:

const path = require('path');

const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const { VueLoaderPlugin } = require('vue-loader');

module.exports = (env, argv) => {
    const distPath = path.resolve('dist');
    const srcPath = path.resolve('src');

    const config = {
        entry: {
            'bundle': path.join(srcPath, 'js/index.js'),
        },
        output: {
            chunkFilename: 'assets/[name].js',
            filename: '[name].js',
            path: distPath
        },
        module: {
            rules: [
                {
                    test: /\.vue$/,
                    exclude: /(node_modules|bower_components)/,
                    use: 'vue-loader',
                },
                {
                    test: /\.js$/,
                    exclude: /(node_modules|bower_components)/,
                    use: {
                        loader: 'babel-loader',
                        options: {
                            plugins: [
                                "@babel/plugin-syntax-dynamic-import",
                                "@babel/plugin-syntax-object-rest-spread",
                            ],
                            presets: [
                                '@babel/preset-env',
                            ],
                            compact: false,
                        }
                    }
                },
                {
                    test: /\.css$/,
                    exclude: /(node_modules|bower_components)/,
                    oneOf: [
                        // this matches `<style module>`
                        {
                            resourceQuery: /module/,
                            use: [
                                MiniCssExtractPlugin.loader,
                                {
                                    loader: 'css-loader',
                                    options: {
                                        modules: true,
                                        localIdentName: '[local]_[hash:base64:5]'
                                    }
                                }
                            ]
                        },
                        // this matches plain `<style>` or `<style scoped>`
                        {
                            use: [
                                MiniCssExtractPlugin.loader,
                                'css-loader'
                            ]
                        }
                    ]
                },
                {
                    test: /\.less$/,
                    exclude: /(node_modules|bower_components)/,
                    oneOf: [
                        // this matches `<style module>`
                        {
                            resourceQuery: /module/,
                            use: [
                                MiniCssExtractPlugin.loader,
                                {
                                    loader: 'css-loader',
                                    options: {
                                        modules: true,
                                        localIdentName: '[local]_[hash:base64:5]'
                                    },
                                },
                                'less-loader',
                            ]
                        },
                        // this matches plain `<style>` or `<style scoped>`
                        {
                            use: [
                                MiniCssExtractPlugin.loader,
                                'css-loader',
                                'less-loader',
                            ]
                        }
                    ]
                },
                {
                    test: /\.woff($|\?)|\.woff2($|\?)|\.ttf($|\?)|\.eot($|\?)|\.svg($|\?)/,
                    use: "file-loader"
                }
            ]
        },
        plugins: [
            new MiniCssExtractPlugin({
                filename: 'bundle.css',
            }),
            new VueLoaderPlugin(),
        ],
        resolve: {
            alias: {
                'vue$': 'vue/dist/vue.esm.js',
            }
        },
    };

    return config;
};

and my main.less like this:

@import "~bootstrap/less/bootstrap.less";
@import "~admin-lte/build/less/AdminLTE.less";

When I try to build my project I get:

FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory
 1: 00007FF704A3285F napi_wrap+119263
 2: 00007FF7049D9526 v8::internal::OrderedHashTable<v8::internal::OrderedHashSet,1>::NextTableOffset+38102
 3: 00007FF7049DA326 node::OnFatalError+438
 4: 00007FF705217AAE v8::Isolate::ReportExternalAllocationLimitReached+94     
 5: 00007FF7051FFC61 v8::SharedArrayBuffer::Externalize+833
 6: 00007FF7050B143C v8::internal::Heap::EphemeronKeyWriteBarrierFromCode+1436
 7: 00007FF7050BC680 v8::internal::Heap::ProtectUnprotectedMemoryChunks+1312  
 8: 00007FF7050B9194 v8::internal::Heap::PageFlagsAreConsistent+3204
 9: 00007FF7050AE993 v8::internal::Heap::CollectGarbage+1283
10: 00007FF7050AD004 v8::internal::Heap::AddRetainedMap+2500
11: 00007FF7050CE48B v8::internal::Factory::NewFixedArrayWithFiller+107
12: 00007FF7050C7317 v8::internal::Factory::InternalizeString<unsigned short>+471
13: 00007FF704F1DD3A v8::internal::HashTable<v8::internal::NumberDictionary,v8::internal::NumberDictionaryShape>::New+122
14: 00007FF7051CA05E v8::internal::Builtins::builtin_handle+317374
15: 00007FF7051C54D8 v8::internal::Builtins::builtin_handle+298040
16: 00007FF7051C503E v8::internal::Builtins::builtin_handle+296862
17: 00007FF70566CD5D v8::internal::SetupIsolateDelegate::SetupHeap+547181
18: 000001C9E13154E6

There doesn't seem to be any way of debugging this. But what I managed to figure out is that LESS seems to get stuck in an endless loop in ~bootstrap/less/mixins/grid-framework.less. This mixin seems to be the culprit:

.loop-grid-columns(@index, @class, @type) when (@index >= 0) {
  .calc-grid-column(@index, @class, @type);
  // next iteration
  .loop-grid-columns((@index - 1), @class, @type);
}

which is weird because of it having a when -guard operator.

iChenLei commented 3 years ago

Thanks for issue report, we will investigate it later.

iChenLei commented 3 years ago

@brainz80 It looks like work as expected.

https://cdn.jsdelivr.net/npm/bootstrap@3.4.1/less/mixins/grid-framework.less

.loop-grid-columns(@index, @class, @type) when (@index >= 0) {
.calc-grid-column(@index, @class, @type);
// next iteration
.loop-grid-columns((@index - 1), @class, @type);
}

which is weird because of it having a when -guard operator.

Not weird, If a loop no when guard, we will get a infinite loop.

brainz80 commented 3 years ago

Ok, managed to figure out what the problem is.

Here's a simple setup:

main.less:

@import 'module1.less';
@import 'module2.less';

module1.less:

.iterate(@index) when (@index >= 0) {
    &.item-@{index} {
        color: rgb(255 - @index, 0, 0);
    }

    .iterate(@index - 1);
}

b {
    .iterate(10);
}

module2.less:

.iterate(@index) when (@index >= 0) {
    &.item-@{index} {
        color: rgb(255 - @index, 0, 0);
    }

    .iterate(@index - 1);
}

a {
    .iterate(20);
}

the problem is that both module1.less and module2.less define the mixin .iterate(). The same thing happens with Bootstrap and AdminLTE. AdminLTE uses a bundled version of Bootstrap and therefore overrides the one imported by:

@import "~bootstrap/less/bootstrap.less";

I'm not sure if this should be the desired result. Maybe the later definition of .iterate() should take precedence?

iChenLei commented 3 years ago

@brainz80 Thanks for detailed description, more clear for me.