VirtoCommerce / vc-platform

Virto Commerce B2B Innovation Platform
https://virtocommerce.com
Other
1.23k stars 846 forks source link

Storefront use gulp for bundles and js minification #1023

Closed tatarincev closed 6 years ago

tatarincev commented 7 years ago

Problems:

Tasks:

OlegoO commented 7 years ago

I recommend to use BundlerMinifier https://github.com/madskristensen/BundlerMinifier

asvishnyakov commented 7 years ago

@OlegoO I prefer gulp. Gulp is more extensible and powerful than just VS Extension. And not all our colleagues use Visual Studio as (s)css editor.

Skrypt commented 7 years ago

I have a gulp setup here if you want to take a look at it. I even configured browser-sync in it. Works pretty well so far. Sent the files to @Woland2k on Gitter.

OlegoO commented 7 years ago

@asvishnyakov BundlerMinifier uses gulp, it is optional utility, it helps to create and configure bundle.config file + create correct file relations in VS + create initial gulp file with config.

Skrypt commented 7 years ago

Here is my gulpfile.js

var fs = require("fs"),
    glob = require("glob"),
    path = require("path-posix"),
    merge = require("merge-stream"),
    gulp = require("gulp"),
    gulpif = require("gulp-if"),
    print = require("gulp-print"),
    debug = require("gulp-debug"),
    newer = require("gulp-newer"),
    plumber = require("gulp-plumber"),
    sourcemaps = require("gulp-sourcemaps"),
    less = require("gulp-less"),
    scss = require("gulp-sass"),
    cssnano = require("gulp-cssnano"),
    typescript = require("gulp-typescript"),
    uglify = require("gulp-uglify"),
    rename = require("gulp-rename"),
    concat = require("gulp-concat"),
    header = require("gulp-header"),
    eol = require("gulp-eol"),
    clean = require("gulp-clean");

//browser sync "css live reload"    
var browserSync = require('browser-sync').create();

// For compat with older versions of Node.js.
require("es6-promise").polyfill();

// To suppress memory leak warning from gulp.watch().
require("events").EventEmitter.prototype._maxListeners = 100;

/*
** GULP TASKS
*/

// Incremental build (each asset group is built only if one or more inputs are newer than the output).
gulp.task("clean", function () {
    var assetGroupTasks = getAssetGroups().map(function (assetGroup) {

        //clean output files
        var cleanFilesStream = gulp.src(assetGroup.outputPath, { read: false })
            .pipe(clean())
    });
});

gulp.task("build", function () {
    var assetGroupTasks = getAssetGroups().map(function (assetGroup) {
        var doRebuild = false;
        return createAssetGroupTask(assetGroup, doRebuild);
    });
    return merge(assetGroupTasks);
});

// Full rebuild (all assets groups are built regardless of timestamps).
gulp.task("rebuild", function () {
    var assetGroupTasks = getAssetGroups().map(function (assetGroup) {
        var doRebuild = true;
        return createAssetGroupTask(assetGroup, doRebuild);
    });
    return merge(assetGroupTasks);
});

// Continuous watch (each asset group is built whenever one of its inputs changes).
gulp.task("watch", function () {
    var pathWin32 = require("path");
    getAssetGroups().forEach(function (assetGroup) {
        var watchPaths = assetGroup.inputPaths.concat(assetGroup.watchPaths);
        var inputWatcher;
        function createWatcher() {
            inputWatcher = gulp.watch(watchPaths, function (event) {
                var isConcat = path.basename(assetGroup.outputFileName, path.extname(assetGroup.outputFileName)) !== "@";
                if (isConcat)
                    console.log("Asset file '" + event.path + "' was " + event.type + ", rebuilding asset group with output '" + assetGroup.outputPath + "'.");
                else
                    console.log("Asset file '" + event.path + "' was " + event.type + ", rebuilding asset group.");
                var doRebuild = true;
                var task = createAssetGroupTask(assetGroup, doRebuild);
            });
        }
        createWatcher();
        gulp.watch(assetGroup.manifestPath, function (event) {
            console.log("Asset manifest file '" + event.path + "' was " + event.type + ", restarting watcher.");
            inputWatcher.remove();
            inputWatcher.end();
            createWatcher();
        });
    });
});

gulp.task("build-sass", function () {
    gulp.src('App_Data/Themes/default/assets/sass/main.scss')
        .pipe(gulp.dest('App_Data/Themes/default/assets/sass/main.css'));

    gulp.src('App_Data/Themes/default/assets/sass/main.scss')
    .pipe(cssnano({
        autoprefixer: { browsers: ["last 2 versions"] },
        discardComments: { removeAll: true },
        discardUnused: false,
        mergeIdents: false,
        reduceIdents: false,
        zindex: false
    }))
    .pipe(gulp.dest('App_Data/Themes/default/assets/sass/main.min.css'));
});

gulp.task("browser-sync", function () {

    browserSync.init({
        proxy: "http://localhost:58215/"
    });

    gulp.watch("./App_Data/Themes/**/{layout,templates}/*.liquid").on('change', browserSync.reload);
    gulp.watch('App_Data/Themes/default/assets/**/*.scss').on('change', browserSync.reload);;

    var pathWin32 = require("path");
    getAssetGroups().forEach(function (assetGroup) {
        var watchPaths = assetGroup.inputPaths.concat(assetGroup.watchPaths);
        var inputWatcher;
        function createWatcher() {
            inputWatcher = gulp.watch(watchPaths, function (event) {
                var isConcat = path.basename(assetGroup.outputFileName, path.extname(assetGroup.outputFileName)) !== "@";
                if (isConcat)
                    console.log("Asset file '" + event.path + "' was " + event.type + ", rebuilding asset group with output '" + assetGroup.outputPath + "'.");
                else
                    console.log("Asset file '" + event.path + "' was " + event.type + ", rebuilding asset group.");
                var doRebuild = true;
                var task = createAssetGroupTask(assetGroup, doRebuild);
            }).on('changed', browserSync.reload);
        }
        createWatcher();
        gulp.watch(assetGroup.manifestPath, function (event) {
            console.log("Asset manifest file '" + event.path + "' was " + event.type + ", restarting watcher.");
            inputWatcher.remove();
            inputWatcher.end();
            createWatcher();
        });
    });
});

/*
** ASSET GROUPS
*/

function getAssetGroups() {
    var assetManifestPaths = glob.sync("./Assets.json", {});
    var customThemePaths = glob.sync("./App_Data/Themes/*/Assets.json");
    assetManifestPaths = assetManifestPaths.concat(customThemePaths);
    var assetGroups = [];
    assetManifestPaths.forEach(function (assetManifestPath) {
        var assetManifest = require("./" + assetManifestPath);
        assetManifest.forEach(function (assetGroup) {
            resolveAssetGroupPaths(assetGroup, assetManifestPath);
            assetGroups.push(assetGroup);
        });
    });
    return assetGroups;
}

function resolveAssetGroupPaths(assetGroup, assetManifestPath) {
    assetGroup.manifestPath = assetManifestPath;
    assetGroup.basePath = path.dirname(assetManifestPath);
    assetGroup.inputPaths = assetGroup.inputs.map(function (inputPath) {
        return path.resolve(path.join(assetGroup.basePath, inputPath));
    });
    assetGroup.watchPaths = [];
    if (!!assetGroup.watch) {
        assetGroup.watchPaths = assetGroup.watch.map(function (watchPath) {
            return path.resolve(path.join(assetGroup.basePath, watchPath));
        });
    }
    assetGroup.outputPath = path.resolve(path.join(assetGroup.basePath, assetGroup.output));
    assetGroup.outputDir = path.dirname(assetGroup.outputPath);
    assetGroup.outputFileName = path.basename(assetGroup.output);
    // Uncomment to copy assets to wwwroot
    //assetGroup.webroot = path.join("./src/Orchard.Cms.Web/wwwroot/", path.basename(assetGroup.basePath), path.dirname(assetGroup.output));
}

function createAssetGroupTask(assetGroup, doRebuild) {
    var outputExt = path.extname(assetGroup.output).toLowerCase();
    var doConcat = path.basename(assetGroup.outputFileName, outputExt) !== "@";
    if (doConcat && !doRebuild) {
        // Force a rebuild of this asset group is the asset manifest file itself is newer than the output.
        var assetManifestStats = fs.statSync(assetGroup.manifestPath);
        var outputStats = fs.existsSync(assetGroup.outputPath) ? fs.statSync(assetGroup.outputPath) : null;
        doRebuild = !outputStats || assetManifestStats.mtime > outputStats.mtime;
    }
    switch (outputExt) {
        case ".css":
            return buildCssPipeline(assetGroup, doConcat, doRebuild);
        case ".js":
            return buildJsPipeline(assetGroup, doConcat, doRebuild);
    }
}

/*
** PROCESSING PIPELINES
*/

function buildCssPipeline(assetGroup, doConcat, doRebuild) {
    assetGroup.inputPaths.forEach(function (inputPath) {
        var ext = path.extname(inputPath).toLowerCase();
        if (ext !== ".scss" && ext !== ".less" && ext !== ".css")
            throw "Input file '" + inputPath + "' is not of a valid type for output file '" + assetGroup.outputPath + "'.";
    });
    var generateSourceMaps = assetGroup.hasOwnProperty("generateSourceMaps") ? assetGroup.generateSourceMaps : true;
    var containsLessOrScss = assetGroup.inputPaths.some(function (inputPath) {
        var ext = path.extname(inputPath).toLowerCase();
        return ext === ".less" || ext === ".scss";
    });

    // Source maps are useless if neither concatenating nor transforming.
    if ((!doConcat || assetGroup.inputPaths.length < 2) && !containsLessOrScss)
        generateSourceMaps = false;
    var minifiedStream = gulp.src(assetGroup.inputPaths) // Minified output, source mapping completely disabled.
        .pipe(gulpif(!doRebuild,
            gulpif(doConcat,
                newer(assetGroup.outputPath),
                newer({
                    dest: assetGroup.outputDir,
                    ext: ".css"
                }))))
        .pipe(plumber())
        .pipe(gulpif(generateSourceMaps, sourcemaps.init()))
        .pipe(gulpif("*.less", less()))
        .pipe(gulpif("*.scss", scss({
            precision: 10,
        })))
        .pipe(gulpif(doConcat, concat(assetGroup.outputFileName)))
        .pipe(cssnano({
            autoprefixer: { browsers: ["last 2 versions"] },
            discardComments: { removeAll: true },
            discardUnused: false,
            mergeIdents: false,
            reduceIdents: false,
            zindex: false
        }))
        .pipe(rename({
            suffix: ".min"
        }))
        .pipe(gulpif(generateSourceMaps, sourcemaps.write(assetGroup.outputDir, {
            sourceMappingURL: function (file) {
                return '/themes/assets/dist/' + file.relative + '.map';
            }
        })))
        .pipe(eol())
        .pipe(gulp.dest(assetGroup.outputDir))
        .pipe(browserSync.stream());
    // Uncomment to copy assets to wwwroot
    //.pipe(gulp.dest(assetGroup.webroot));
    var devStream = gulp.src(assetGroup.inputPaths) // Non-minified output, with source mapping
        .pipe(gulpif(!doRebuild,
            gulpif(doConcat,
                newer(assetGroup.outputPath),
                newer({
                    dest: assetGroup.outputDir,
                    ext: ".css"
                }))))
        .pipe(plumber())
        .pipe(gulpif(generateSourceMaps, sourcemaps.init()))
        .pipe(gulpif("*.less", less()))
        .pipe(gulpif("*.scss", scss({
            precision: 10
        })))
        .pipe(gulpif(doConcat, concat(assetGroup.outputFileName)))
        .pipe(header(
            "/*\n" +
            "** NOTE: This file is generated by Gulp and should not be edited directly!\n" +
            "** Any changes made directly to this file will be overwritten next time its asset group is processed by Gulp.\n" +
            "*/\n\n"))
        .pipe(gulpif(generateSourceMaps, sourcemaps.write(assetGroup.outputDir, {
            sourceMappingURL: function (file) {
                return '/themes/assets/dist/' + file.relative + '.map';
            }
        })))
        .pipe(eol())
        .pipe(gulp.dest(assetGroup.outputDir))
        .pipe(browserSync.stream());
    // Uncomment to copy assets to wwwroot
    //.pipe(gulp.dest(assetGroup.webroot));
    return merge([minifiedStream, devStream]);
}

function buildJsPipeline(assetGroup, doConcat, doRebuild) {
    assetGroup.inputPaths.forEach(function (inputPath) {
        var ext = path.extname(inputPath).toLowerCase();
        if (ext !== ".ts" && ext !== ".js")
            throw "Input file '" + inputPath + "' is not of a valid type for output file '" + assetGroup.outputPath + "'.";
    });
    var generateSourceMaps = assetGroup.hasOwnProperty("generateSourceMaps") ? assetGroup.generateSourceMaps : true;
    // Source maps are useless if neither concatenating nor transforming.
    if ((!doConcat || assetGroup.inputPaths.length < 2) && !assetGroup.inputPaths.some(function (inputPath) { return path.extname(inputPath).toLowerCase() === ".ts"; }))
        generateSourceMaps = false;
    return gulp.src(assetGroup.inputPaths)
        .pipe(gulpif(!doRebuild,
            gulpif(doConcat,
                newer(assetGroup.outputPath),
                newer({
                    dest: assetGroup.outputDir,
                    ext: ".js"
                }))))
        .pipe(gulpif(generateSourceMaps, sourcemaps.init()))
        .pipe(plumber())
        .pipe(gulpif("*.ts", typescript({
            declaration: false,
            noImplicitAny: true,
            noEmitOnError: true,
            sortOutput: true,
        }).js))
        .pipe(gulpif(doConcat, concat(assetGroup.outputFileName)))
        .pipe(header(
            "/*\n" +
            "** NOTE: This file is generated by Gulp and should not be edited directly!\n" +
            "** Any changes made directly to this file will be overwritten next time its asset group is processed by Gulp.\n" +
            "*/\n\n"))
        .pipe(gulp.dest(assetGroup.outputDir))
        // Uncomment to copy assets to wwwroot
        //.pipe(gulp.dest(assetGroup.webroot))
        .pipe(uglify())
        .pipe(rename({
            suffix: ".min"
        }))
        .pipe(gulpif(generateSourceMaps, sourcemaps.write(assetGroup.outputDir, {
            sourceMappingURL: function (file) {
                return '/themes/assets/dist/' + file.relative + '.map';
            }
        })))
        .pipe(eol())
        .pipe(gulp.dest(assetGroup.outputDir))
    // Uncomment to copy assets to wwwroot
    //.pipe(gulp.dest(assetGroup.webroot));
}

here is a Asset.json example file

[
  {
    "generateSourceMaps": false,
    "inputs": [
      "node_modules/font-awesome/css/font-awesome.css",
      "App_Data/Themes/default/assets/css/angular-material-modified.css",
      "node_modules/float-labels.js/dist/float-labels.css",
      "node_modules/angular-floating-label/dist/floating-label.css"
    ],
    "output": "App_Data/Themes/default/assets/dist/dependencies.css"
  },
  {
    "generateSourceMaps": false,
    "inputs": [
      "node_modules/jquery/dist/jquery.js",
      "node_modules/jquery.cookie/jquery.cookie.js",
      "node_modules/angular/angular.js",
      "node_modules/angular-animate/angular-animate.js",
      "node_modules/angular-touch/angular-touch.js",
      "node_modules/angular-cookies/angular-cookies.js",
      "node_modules/angular-aria/angular-aria.js",
      "node_modules/angular-messages/angular-messages.js",
      "node_modules/angular-resource/angular-resource.js",
      "node_modules/angular-filter/dist/angular-filter.js",
      "App_Data/Themes/default/assets/js/angular-stripe-4.2.12/release/angular-stripe.js",
      "node_modules/angular-plaid-link/src/angular-plaid-link.js",
      "node_modules/crypto-js/crypto-js.js",
      "node_modules/@uirouter/angularjs/release/angular-ui-router.js",
      "node_modules/angular-sanitize/angular-sanitize.js",
      "node_modules/angular-translate/dist/angular-translate.js",
      "node_modules/angular-translate-loader-url/angular-translate-loader-url.js",
      "node_modules/angular-translate-loader-static-files/angular-translate-loader-static-files.js",
      "node_modules/angular-i18n/angular-locale_en-us.js",
      "App_Data/Themes/default/assets/js/angular-credit-cards-3.1.6/release/angular-credit-cards.js",
      "node_modules/angular-floating-label/dist/floating-label.js",
      "node_modules/angular-material/angular-material.js",
      "node_modules/angular-ui-bootstrap/dist/ui-bootstrap-tpls.js",
      "node_modules/underscore/underscore.js",
      "node_modules/html2canvas/dist/html2canvas.js",
      "node_modules/pdfmake/build/pdfmake.js"
    ],
    "output": "App_Data/Themes/default/assets/dist/dependencies.js"
  }
]