Base solution for your next web application
Open Closed

npm run build does not minify bundles #9458


User avatar
0
compassinformatics17 created

Hi Guys,

I am coming to the end of the dev stage of an application and want to deploy to QA/UAT. As part of this process I want to run "npm run build". I followed the process outlined here https://docs.aspnetzero.com/en/aspnet-core-mvc/latest/Setting-Up-an-Azure-Pipeline-Mvc-Core for yarn and "npm run build" in DevOps. As you can see below, "npm run build" executes correctly with no errors, but the created bundles are identical to the dev versions and are not minified. I notice that this was a bug in the past #6987. Is it possible this bug has reappeared?

I'm using version 8.9.2 of the framework.

Thanks, Barry.

2020-08-04T13:43:54.4659893Z [14:43:54] Using gulpfile H:\Agents\1\_work\67\s\src\MyProject.Web.Mvc\gulpfile.js
2020-08-04T13:43:54.4721122Z [14:43:54] Starting 'build'...
2020-08-04T13:47:48.3216910Z [14:47:48] Finished 'build' after 3.88 min
2020-08-04T13:47:48.4513226Z ##[section]Finishing: NPM Run Build task

11 Answer(s)
  • User Avatar
    0
    maliming created
    Support Team

    hi

    Can you share your gulpfile.js file?

  • User Avatar
    0
    compassinformatics17 created

    No problem, thanks for getting back to me. Note the small function at the end which makes the files editable. This gets around the TFS locking issue for bundle generation.

    var gulp = require("gulp");
    var path = require('path');
    var merge = require("merge-stream");
    var globby = require('globby');
    var concat = require('gulp-concat');
    var less = require('gulp-less');
    var uglify = require('gulp-uglify-es').default;
    var cleanCss = require('gulp-clean-css');
    var postcss = require("gulp-postcss");
    var url = require("postcss-url");
    
    var bundleConfig = require(path.resolve(__dirname, 'bundles.json'));
    var production = false;
    
    const { watch } = require('gulp');
    
    var styleEntries = {};
    var scriptEntries = {};
    
    var viewScripts = globby.sync([
        './wwwroot/view-resources/**/*.js',
        '!./wwwroot/view-resources/**/*.min.js'
    ]);
    
    var viewStyles = globby.sync([
        './wwwroot/view-resources/**/*.css',
        './wwwroot/view-resources/**/*.less',
        '!./wwwroot/view-resources/**/*.min.css'
    ]);
    
    var metronicScripts = globby.sync([
        './wwwroot/metronic/**/*.js',
        '!./wwwroot/metronic/**/*.min.js',
        '!./wwwroot/metronic/core/**/*.js'
    ]);
    
    var metronicStyles = globby.sync([
        './wwwroot/metronic/**/*.css',
        './wwwroot/metronic/**/*.less',
        '!./wwwroot/metronic/**/*.min.css'
    ]);
    
    function processInputDefinition(input) {
        var result = [];
        for (var i = 0; i < input.length; i++) {
            var url = input[i];
            if (url.startsWith('!')) {
                result.push('!' + path.resolve(__dirname, url.substring(1)));
            } else {
                result.push(path.resolve(__dirname, url));
            }
        }
    
        return result;
    }
    
    function fillScriptBundles() {
        // User defined bundles
        for (var k = 0; k < bundleConfig.scripts.length; k++) {
            var scriptBundle = bundleConfig.scripts[k];
            scriptEntries[scriptBundle.output] = globby.sync(processInputDefinition(scriptBundle.input), { noext: true });
        }
    
        // View scripts
        for (var i = 0; i < viewScripts.length; i++) {
            var viewScriptName = viewScripts[i].replace('./wwwroot/', '');
            scriptEntries[viewScriptName.replace('.js', '.min.js')] = [path.resolve(__dirname, viewScripts[i])];
        }
    
        // Metronic scripts
        for (var j = 0; j < metronicScripts.length; j++) {
            var metronicScriptName = metronicScripts[j].replace('./wwwroot/', '');
            scriptEntries[metronicScriptName.replace('.js', '.min.js')] = [path.resolve(__dirname, metronicScripts[j])];
        }
    }
    
    function fillStyleBundles() {
        // User defined styles
        for (var k = 0; k < bundleConfig.styles.length; k++) {
            var styleBundle = bundleConfig.styles[k];
            styleEntries[styleBundle.output] = globby.sync(processInputDefinition(styleBundle.input), { noext: true });
        }
    
        // View styles
        for (var j = 0; j < viewStyles.length; j++) {
            var viewStyleName = viewStyles[j].replace('./wwwroot/', '');
    
            if (viewStyleName.indexOf('.css') >= 0) {
                styleEntries[viewStyleName.replace('.css', '.min.css')] = [path.resolve(__dirname, 'wwwroot/' + viewStyleName)];
            }
    
            if (viewStyleName.indexOf('.less') >= 0) {
                styleEntries[viewStyleName.replace('.less', '.min.css')] = [path.resolve(__dirname, 'wwwroot/' + viewStyleName)];
            }
        }
    
        // Metronic styles
        for (var i = 0; i < metronicStyles.length; i++) {
            var metronicStyleName = metronicStyles[i].replace('./wwwroot/', '');
    
            if (metronicStyleName.indexOf('.css') >= 0) {
                styleEntries[metronicStyleName.replace('.css', '.min.css')] =
                    [path.resolve(__dirname, 'wwwroot/' + metronicStyleName)];
            }
    
            if (metronicStyleName.indexOf('.less') >= 0) {
                styleEntries[metronicStyleName.replace('.less', '.min.css')] = [path.resolve(__dirname, 'wwwroot/' + metronicStyleName)];
            }
        }
    }
    
    function getFileNameFromPath(fullPath) {
        return path.basename(fullPath);
    }
    
    function getPathWithoutFileNameFromPath(fullPath) {
        return path.dirname(fullPath);
    }
    
    function fillScriptMappings() {
        for (var k = 0; k < bundleConfig.scriptMappings.length; k++) {
            var scriptBundle = bundleConfig.scriptMappings[k];
            var inputFilesToBeCopied = globby.sync(processInputDefinition(scriptBundle.input), { noext: true });
            for (var j = 0; j < inputFilesToBeCopied.length; j++) {
                var outputFileName = path.join(scriptBundle.outputFolder, getFileNameFromPath(inputFilesToBeCopied[j]));
                scriptEntries[outputFileName] = [inputFilesToBeCopied[j]];
            }
        }
    }
    
    function createScriptBundles() {
        var tasks = [];
        for (var script in scriptEntries) {
            tasks.push(
                createScriptBundle(script)
            );
        }
    
        return tasks;
    }
    
    function createScriptBundle(script) {
        var bundleName = getFileNameFromPath(script);
        var bundlePath = getPathWithoutFileNameFromPath(script);
    
        var stream = gulp.src(scriptEntries[script]);
    
        if (production) {
            stream = stream
                .pipe(uglify());
        }
    
        return stream.pipe(concat(bundleName))
            .pipe(gulp.dest('wwwroot/' + bundlePath));
    }
    
    function createStyleBundles() {
        var tasks = [];
        for (var style in styleEntries) {
            tasks.push(
                createStyleBundle(style)
            );
        }
    
        return tasks;
    }
    
    function createStyleBundle(style) {
    
        var bundleName = getFileNameFromPath(style);
        var bundlePath = getPathWithoutFileNameFromPath(style);
    
        var options = {
            url: function (asset) {
                // Ignore absolute URLs
                if (asset.url.substring(0, 1) === '/') {
                    return asset.url;
                }
    
                var outputFolder = '';
    
                if (asset.url.match(/\.(png|svg|jpg|gif)$/)) {
                    outputFolder = 'dist/img';
                } else if (asset.url.match(/\.(woff|woff2|eot|ttf|otf)[?]{0,1}.*$/)) {
                    outputFolder = 'dist/fonts';
                } else {
                    // Ignore not recognized assets like data:image etc...
                    return asset.url;
                }
    
                var fileName = path.basename(asset.absolutePath);
                var outputPath = path.join(__dirname, '/wwwroot/' + outputFolder + '/');
    
                gulp.src(asset.absolutePath).pipe(gulp.dest(outputPath));
    
                return '/' + outputFolder + '/' + fileName;
            }
        };
    
        var stream = gulp.src(styleEntries[style])
            .pipe(postcss([url(options)]))
            .pipe(less({ math: 'parens-division' }));
    
        if (production) {
            stream = stream.pipe(cleanCss());
        }
    
        return stream
            .pipe(concat(bundleName))
            .pipe(gulp.dest('wwwroot/' + bundlePath));
    }
    
    function findMatchingElements(path, array) {
        var result = [];
        for (var item in array) {
            if (array[item].indexOf(path) >= 0) {
                result[item] = array[item];
            }
        }
    
        return result;
    }
    
    function watchScriptEntries() {
        for (var script in scriptEntries) {
            var watcher = watch(scriptEntries[script]);
    
            watcher.on('change', function (path, stats) {
                console.log(`${path} updated`);
    
                var changedBundles = findMatchingElements(path, scriptEntries);
    
                for (var changedBundle in changedBundles) {
                    createScriptBundle(changedBundle);
                }
    
            });
        }
    }
    
    function watchStyleEntries() {
        for (var style in styleEntries) {
            var watcher = watch(styleEntries[style]);
    
            watcher.on('change', function (path, stats) {
                console.log(`${path} updated`);
    
                var changedBundles = findMatchingElements(path, styleEntries);
    
                for (var changedBundle in changedBundles) {
                    createStyleBundle(changedBundle);
                }
    
            });
        }
    }
    
    function build() {
    
        production = true;
    
        fillScriptBundles();
        fillStyleBundles();
        fillScriptMappings();
    
        var scriptTasks = createScriptBundles();
        var styleTasks = createStyleBundles();
    
        return merge(scriptTasks.concat(styleTasks));
    }
    
    function buildDev() {
    
        fillScriptBundles();
        fillStyleBundles();
        fillScriptMappings();
    
        var scriptTasks = createScriptBundles();
        var styleTasks = createStyleBundles();
    
        watchScriptEntries();
        watchStyleEntries();
    
        console.log('Bundles are being created, please wait...');
    
        return merge(scriptTasks.concat(styleTasks));
    }
    
    function clearReadOnlyAttribute(path) {
        console.log('Ensuring files are editable, please wait...');
        require('child_process').exec('attrib -r "' + path + '\*.*" /s /d');
    }
    
    // boneill: Make all files and folders in wwwroot editable. Fixes TFS locking issue for bundle generation.
    clearReadOnlyAttribute('./wwwroot/');
    
    exports.build = build;
    exports.buildDev = buildDev;
    
  • User Avatar
    0
    maliming created
    Support Team

    Hi

    The gulpfile.js is no problem. Have you tried the zero demo project?

  • User Avatar
    0
    compassinformatics17 created

    Hi,

    OK I did some more investigation on this. It works correctly if I run it locally on my dev machine. But when run in devops, the build process completes without errors but the files are not changed. I am still trying to ascertain the cause, so if anyone else has experienced the same issue, I would be interested in hearing how it was resolved.

  • User Avatar
    0
    ismcagdas created
    Support Team

    Hi @compassinformatics17

    Is your DEV and DevOps environments has the same Node version ?

  • User Avatar
    0
    compassinformatics17 created

    Hi Sorry for the late reply.

    I'm using Yarn to handle dependencies as specified in this AspNetZero Tutorial. All works fine but the files are not changed on the azure version.

    Below are a few images detailing the config and build task output in DevOps. It runs fine but the JS files are not minified in the artefact once the build completes on DevOps. I cannot see what's amiss and why the files are not being minified since all appears correct to my eyes.

    Thanks, Barry.

  • User Avatar
    0
    ismcagdas created
    Support Team

    Hi @compassinformatics17

    Does the min versions of the files exist in your Azure environment before you run the npm run build command ? If so, can you try to delete one of those min files and run the pipeline again ? This might be related to a file write permission.

    Thanks,

  • User Avatar
    0
    ISTeam created

    Hello @compassinformatics17 and @ismcagdas

    The gulp task in my azure pipeline previously working fine but as it was taking too much time and we didn't have frequent changes in theme, I disabled that task. But now after enabling I am facing similar issue and the files are not getting minified. In Visual Studio using Task Runner gulp file makes minified files and applies theme related changes but it does not work in the Azure DevOps pipeline.

    Did you guys finally found any workaround in the pipeline task or fix? Please advise.

    Thanks.

  • User Avatar
    0
    ismcagdas created
    Support Team

    Hi @ISTeam

    Could you check if files in the output folder are cleared before creating new bundles ?

  • User Avatar
    0
    ISTeam created

    Thank you for your reply @ismcagdas

    Unfortunately I am not sure which output folder you are mentioning. I tried to manually delete entire wwwroot directory from the deployment folder on our on-premises machine but that didn't help.

    The CI pipeline I have created last year with mostly v8.1.0 was using graphical interface in Azure DevOps as per the documentation. So, I tried to set 'Source and Output directory' cleaning suggested in this SO post answer.

    After two release I verified that all the CSS/JS including min files under the wwwroot subdirectories are updated but still the header title and its padding are somewhat different in the production.

    But this is how it looks in development env and

    something different in production

    @Update: Finally I found that it was because of Visual Settings => Subheader => Subheader style was different. In dev it was "Solid" and in production it was "Transparent" causing the padding like difference. Wondering as this was the root cause of the problem and initially we guess that this setting might be something related to background color only!

  • User Avatar
    0
    ismcagdas created
    Support Team

    Hi @ISTeam

    I'm glad that it is solved :)