Closed tatarincev closed 6 years ago
I recommend to use BundlerMinifier https://github.com/madskristensen/BundlerMinifier
@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.
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.
@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.
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"
}
]
Problems:
Tasks: