Copybara bot | be50d49 | 2023-11-30 00:16:42 +0100 | [diff] [blame] | 1 | /** |
| 2 | * |
| 3 | * Material Design Lite |
| 4 | * Copyright 2015 Google Inc. All rights reserved. |
| 5 | * |
| 6 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 7 | * you may not use this file except in compliance with the License. |
| 8 | * You may obtain a copy of the License at |
| 9 | * |
| 10 | * https://www.apache.org/licenses/LICENSE-2.0 |
| 11 | * |
| 12 | * Unless required by applicable law or agreed to in writing, software |
| 13 | * distributed under the License is distributed on an "AS IS" BASIS, |
| 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 15 | * See the License for the specific language governing permissions and |
| 16 | * limitations under the License |
| 17 | * |
| 18 | */ |
| 19 | |
| 20 | // jscs:disable jsDoc |
| 21 | |
| 22 | 'use strict'; |
| 23 | |
| 24 | // Include Gulp & Tools We'll Use |
| 25 | import fs from 'fs'; |
| 26 | import path from 'path'; |
| 27 | import mergeStream from 'merge-stream'; |
| 28 | import del from 'del'; |
| 29 | import vinylPaths from 'vinyl-paths'; |
| 30 | import runSequence from 'run-sequence'; |
| 31 | import browserSync from 'browser-sync'; |
| 32 | import through from 'through2'; |
| 33 | import swig from 'swig'; |
| 34 | import gulp from 'gulp'; |
| 35 | import closureCompiler from 'gulp-closure-compiler'; |
| 36 | import gulpLoadPlugins from 'gulp-load-plugins'; |
| 37 | import uniffe from './utils/uniffe.js'; |
| 38 | import pkg from './package.json'; |
| 39 | |
| 40 | const $ = gulpLoadPlugins(); |
| 41 | const reload = browserSync.reload; |
| 42 | const hostedLibsUrlPrefix = 'https://code.getmdl.io'; |
| 43 | const templateArchivePrefix = 'mdl-template-'; |
| 44 | const bucketProd = 'gs://www.getmdl.io'; |
| 45 | const bucketStaging = 'gs://mdl-staging'; |
| 46 | const bucketCode = 'gs://code.getmdl.io'; |
| 47 | const banner = ['/**', |
| 48 | ' * <%= pkg.name %> - <%= pkg.description %>', |
| 49 | ' * @version v<%= pkg.version %>', |
| 50 | ' * @license <%= pkg.license %>', |
| 51 | ' * @copyright 2015 Google, Inc.', |
| 52 | ' * @link https://github.com/google/material-design-lite', |
| 53 | ' */', |
| 54 | ''].join('\n'); |
| 55 | |
| 56 | let codeFiles = ''; |
| 57 | |
| 58 | const AUTOPREFIXER_BROWSERS = [ |
| 59 | 'ie >= 10', |
| 60 | 'ie_mob >= 10', |
| 61 | 'ff >= 30', |
| 62 | 'chrome >= 34', |
| 63 | 'safari >= 7', |
| 64 | 'opera >= 23', |
| 65 | 'ios >= 7', |
| 66 | 'android >= 4.4', |
| 67 | 'bb >= 10' |
| 68 | ]; |
| 69 | |
| 70 | const SOURCES = [ |
| 71 | // Component handler |
| 72 | 'src/mdlComponentHandler.js', |
| 73 | // Polyfills/dependencies |
| 74 | 'src/third_party/**/*.js', |
| 75 | // Base components |
| 76 | 'src/button/button.js', |
| 77 | 'src/checkbox/checkbox.js', |
| 78 | 'src/icon-toggle/icon-toggle.js', |
| 79 | 'src/menu/menu.js', |
| 80 | 'src/progress/progress.js', |
| 81 | 'src/radio/radio.js', |
| 82 | 'src/slider/slider.js', |
| 83 | 'src/snackbar/snackbar.js', |
| 84 | 'src/spinner/spinner.js', |
| 85 | 'src/switch/switch.js', |
| 86 | 'src/tabs/tabs.js', |
| 87 | 'src/textfield/textfield.js', |
| 88 | 'src/tooltip/tooltip.js', |
| 89 | // Complex components (which reuse base components) |
| 90 | 'src/layout/layout.js', |
| 91 | 'src/data-table/data-table.js', |
| 92 | // And finally, the ripples |
| 93 | 'src/ripple/ripple.js' |
| 94 | ]; |
| 95 | |
| 96 | // ***** Development tasks ****** // |
| 97 | |
| 98 | // Lint JavaScript |
| 99 | gulp.task('lint', () => { |
| 100 | return gulp.src([ |
| 101 | 'src/**/*.js', |
| 102 | 'gulpfile.babel.js' |
| 103 | ]) |
| 104 | .pipe(reload({stream: true, once: true})) |
| 105 | .pipe($.jshint()) |
| 106 | .pipe($.jscs()) |
| 107 | .pipe($.jshint.reporter('jshint-stylish')) |
| 108 | .pipe($.jscs.reporter()) |
| 109 | .pipe($.if(!browserSync.active, $.jshint.reporter('fail'))) |
| 110 | .pipe($.if(!browserSync.active, $.jscs.reporter('fail'))); |
| 111 | }); |
| 112 | |
| 113 | // ***** Production build tasks ****** // |
| 114 | |
| 115 | // Optimize Images |
| 116 | // TODO: Update image paths in final CSS to match root/images |
| 117 | gulp.task('images', () => { |
| 118 | return gulp.src('src/**/*.{svg,png,jpg}') |
| 119 | .pipe($.flatten()) |
| 120 | .pipe($.cache($.imagemin({ |
| 121 | progressive: true, |
| 122 | interlaced: true |
| 123 | }))) |
| 124 | .pipe(gulp.dest('dist/images')) |
| 125 | .pipe($.size({title: 'images'})); |
| 126 | }); |
| 127 | |
| 128 | // Compile and Automatically Prefix Stylesheets (dev) |
| 129 | gulp.task('styles:dev', () => { |
| 130 | return gulp.src('src/**/*.scss') |
| 131 | .pipe($.sass({ |
| 132 | precision: 10, |
| 133 | onError: console.error.bind(console, 'Sass error:') |
| 134 | })) |
| 135 | .pipe($.cssInlineImages({ |
| 136 | webRoot: 'src' |
| 137 | })) |
| 138 | .pipe($.autoprefixer(AUTOPREFIXER_BROWSERS)) |
| 139 | .pipe(gulp.dest('.tmp/styles')) |
| 140 | .pipe($.size({title: 'styles'})); |
| 141 | }); |
| 142 | |
| 143 | // Compile and Automatically Prefix Stylesheet Templates (production) |
| 144 | gulp.task('styletemplates', () => { |
| 145 | // For best performance, don't add Sass partials to `gulp.src` |
| 146 | return gulp.src('src/template.scss') |
| 147 | // Generate Source Maps |
| 148 | .pipe($.sourcemaps.init()) |
| 149 | .pipe($.sass({ |
| 150 | precision: 10, |
| 151 | onError: console.error.bind(console, 'Sass error:') |
| 152 | })) |
| 153 | .pipe($.cssInlineImages({webRoot: 'src'})) |
| 154 | .pipe($.autoprefixer(AUTOPREFIXER_BROWSERS)) |
| 155 | .pipe(gulp.dest('.tmp')) |
| 156 | // Concatenate Styles |
| 157 | .pipe($.concat('material.css.template')) |
| 158 | .pipe(gulp.dest('dist')) |
| 159 | // Minify Styles |
| 160 | .pipe($.if('*.css.template', $.csso())) |
| 161 | .pipe($.concat('material.min.css.template')) |
| 162 | .pipe($.header(banner, {pkg})) |
| 163 | .pipe($.sourcemaps.write('.')) |
| 164 | .pipe(gulp.dest('dist')) |
| 165 | .pipe($.size({title: 'styles'})); |
| 166 | }); |
| 167 | |
| 168 | // Compile and Automatically Prefix Stylesheets (production) |
| 169 | gulp.task('styles', () => { |
| 170 | // For best performance, don't add Sass partials to `gulp.src` |
| 171 | return gulp.src('src/material-design-lite.scss') |
| 172 | // Generate Source Maps |
| 173 | .pipe($.sourcemaps.init()) |
| 174 | .pipe($.sass({ |
| 175 | precision: 10, |
| 176 | onError: console.error.bind(console, 'Sass error:') |
| 177 | })) |
| 178 | .pipe($.cssInlineImages({webRoot: 'src'})) |
| 179 | .pipe($.autoprefixer(AUTOPREFIXER_BROWSERS)) |
| 180 | .pipe(gulp.dest('.tmp')) |
| 181 | // Concatenate Styles |
| 182 | .pipe($.concat('material.css')) |
| 183 | .pipe($.header(banner, {pkg})) |
| 184 | .pipe(gulp.dest('dist')) |
| 185 | // Minify Styles |
| 186 | .pipe($.if('*.css', $.csso())) |
| 187 | .pipe($.concat('material.min.css')) |
| 188 | .pipe($.header(banner, {pkg})) |
| 189 | .pipe($.sourcemaps.write('.')) |
| 190 | .pipe(gulp.dest('dist')) |
| 191 | .pipe($.size({title: 'styles'})); |
| 192 | }); |
| 193 | |
| 194 | // Only generate CSS styles for the MDL grid |
| 195 | gulp.task('styles-grid', () => { |
| 196 | return gulp.src('src/material-design-lite-grid.scss') |
| 197 | .pipe($.sass({ |
| 198 | precision: 10, |
| 199 | onError: console.error.bind(console, 'Sass error:') |
| 200 | })) |
| 201 | .pipe($.autoprefixer(AUTOPREFIXER_BROWSERS)) |
| 202 | .pipe(gulp.dest('.tmp')) |
| 203 | // Concatenate Styles |
| 204 | .pipe($.concat('material-grid.css')) |
| 205 | .pipe($.header(banner, {pkg})) |
| 206 | .pipe(gulp.dest('dist')) |
| 207 | // Minify Styles |
| 208 | .pipe($.if('*.css', $.csso())) |
| 209 | .pipe($.concat('material-grid.min.css')) |
| 210 | .pipe($.header(banner, {pkg})) |
| 211 | .pipe(gulp.dest('dist')) |
| 212 | .pipe($.size({title: 'styles-grid'})); |
| 213 | }); |
| 214 | |
| 215 | // Build with Google's Closure Compiler, requires Java 1.7+ installed. |
| 216 | gulp.task('closure', () => { |
| 217 | return gulp.src(SOURCES) |
| 218 | .pipe(closureCompiler({ |
| 219 | compilerPath: 'node_modules/google-closure-compiler/compiler.jar', |
| 220 | fileName: 'material.closure.min.js', |
| 221 | compilerFlags: { |
| 222 | // jscs:disable requireCamelCaseOrUpperCaseIdentifiers |
| 223 | compilation_level: 'ADVANCED_OPTIMIZATIONS', |
| 224 | language_in: 'ECMASCRIPT6_STRICT', |
| 225 | language_out: 'ECMASCRIPT5_STRICT', |
| 226 | warning_level: 'VERBOSE' |
| 227 | // jscs:enable requireCamelCaseOrUpperCaseIdentifiers |
| 228 | } |
| 229 | })) |
| 230 | .pipe(gulp.dest('./dist')); |
| 231 | }); |
| 232 | |
| 233 | // Concatenate And Minify JavaScript |
| 234 | gulp.task('scripts', ['lint'], () => { |
| 235 | return gulp.src(SOURCES) |
| 236 | .pipe($.if(/mdlComponentHandler\.js/, $.util.noop(), uniffe())) |
| 237 | .pipe($.sourcemaps.init()) |
| 238 | // Concatenate Scripts |
| 239 | .pipe($.concat('material.js')) |
| 240 | .pipe($.iife({useStrict: true})) |
| 241 | .pipe(gulp.dest('dist')) |
| 242 | // Minify Scripts |
| 243 | .pipe($.uglify({ |
| 244 | sourceRoot: '.', |
| 245 | sourceMapIncludeSources: true |
| 246 | })) |
| 247 | .pipe($.header(banner, {pkg})) |
| 248 | .pipe($.concat('material.min.js')) |
| 249 | // Write Source Maps |
| 250 | .pipe($.sourcemaps.write('.')) |
| 251 | .pipe(gulp.dest('dist')) |
| 252 | .pipe($.size({title: 'scripts'})); |
| 253 | }); |
| 254 | |
| 255 | // Clean Output Directory |
| 256 | gulp.task('clean', () => del(['dist', '.publish'])); |
| 257 | |
| 258 | // Copy package manger and LICENSE files to dist |
| 259 | gulp.task('metadata', () => { |
| 260 | return gulp.src([ |
| 261 | 'package.json', |
| 262 | 'bower.json', |
| 263 | 'LICENSE' |
| 264 | ]) |
| 265 | .pipe(gulp.dest('dist')); |
| 266 | }); |
| 267 | |
| 268 | // Build Production Files, the Default Task |
| 269 | gulp.task('default', ['clean'], cb => { |
| 270 | runSequence( |
| 271 | ['styles', 'styles-grid'], |
| 272 | ['scripts'], |
| 273 | ['mocha'], |
| 274 | cb); |
| 275 | }); |
| 276 | |
| 277 | // Build production files and microsite |
| 278 | gulp.task('all', ['clean'], cb => { |
| 279 | runSequence( |
| 280 | ['styletemplates'], |
| 281 | ['styles-grid', 'styles:gen'], |
| 282 | ['scripts'], |
| 283 | ['mocha'], |
| 284 | ['assets', 'pages', |
| 285 | 'templates', 'images', 'metadata'], |
| 286 | ['zip'], |
| 287 | cb); |
| 288 | }); |
| 289 | |
| 290 | // ***** Testing tasks ***** // |
| 291 | |
| 292 | gulp.task('mocha', ['styles'], () => { |
| 293 | return gulp.src('test/index.html') |
| 294 | .pipe($.mochaPhantomjs({reporter: 'tap'})); |
| 295 | }); |
| 296 | |
| 297 | gulp.task('mocha:closure', ['closure'], () => { |
| 298 | return gulp.src('test/index.html') |
| 299 | .pipe($.replace('src="../dist/material.js"', |
| 300 | 'src="../dist/material.closure.min.js"')) |
| 301 | .pipe($.rename('temp.html')) |
| 302 | .pipe(gulp.dest('test')) |
| 303 | .pipe($.mochaPhantomjs({reporter: 'tap'})) |
| 304 | .on('finish', () => del.sync('test/temp.html')) |
| 305 | .on('error', () => del.sync('test/temp.html')); |
| 306 | }); |
| 307 | |
| 308 | gulp.task('test', [ |
| 309 | 'lint', |
| 310 | 'mocha', |
| 311 | 'mocha:closure' |
| 312 | ]); |
| 313 | |
| 314 | gulp.task('test:visual', () => { |
| 315 | browserSync({ |
| 316 | notify: false, |
| 317 | server: '.', |
| 318 | startPath: 'test/visual/index.html' |
| 319 | }); |
| 320 | |
| 321 | gulp.watch('test/visual/**', reload); |
| 322 | }); |
| 323 | |
| 324 | // ***** Landing page tasks ***** // |
| 325 | |
| 326 | /** |
| 327 | * Site metadata for use with templates. |
| 328 | * @type {Object} |
| 329 | */ |
| 330 | const site = {}; |
| 331 | |
| 332 | /** |
| 333 | * Generates an HTML file based on a template and file metadata. |
| 334 | */ |
| 335 | function applyTemplate() { |
| 336 | return through.obj((file, enc, cb) => { |
| 337 | const data = { |
| 338 | site, |
| 339 | page: file.page, |
| 340 | content: file.contents.toString() |
| 341 | }; |
| 342 | |
| 343 | const templateFile = path.join( |
| 344 | __dirname, 'docs', '_templates', `${file.page.layout}.html`); |
| 345 | const tpl = swig.compileFile(templateFile, {cache: false}); |
| 346 | |
| 347 | file.contents = new Buffer(tpl(data)); |
| 348 | cb(null, file); |
| 349 | }); |
| 350 | } |
| 351 | |
| 352 | /** |
| 353 | * Generates an index.html file for each README in MDL/src directory. |
| 354 | */ |
| 355 | gulp.task('components', ['demos'], () => { |
| 356 | return gulp.src('src/**/README.md', {base: 'src'}) |
| 357 | // Add basic front matter. |
| 358 | .pipe($.header('---\nlayout: component\nbodyclass: component\ninclude_prefix: ../../\n---\n\n')) |
| 359 | .pipe($.frontMatter({ |
| 360 | property: 'page', |
| 361 | remove: true |
| 362 | })) |
| 363 | .pipe($.marked()) |
| 364 | .pipe((() => { |
| 365 | return through.obj((file, enc, cb) => { |
| 366 | file.page.component = file.relative.split('/')[0]; |
| 367 | cb(null, file); |
| 368 | }); |
| 369 | })()) |
| 370 | .pipe(applyTemplate()) |
| 371 | .pipe($.rename(path => path.basename = 'index')) |
| 372 | .pipe(gulp.dest('dist/components')); |
| 373 | }); |
| 374 | |
| 375 | /** |
| 376 | * Copies demo files from MDL/src directory. |
| 377 | */ |
| 378 | gulp.task('demoresources', () => { |
| 379 | return gulp.src([ |
| 380 | 'src/**/demos.css', |
| 381 | 'src/**/demo.css', |
| 382 | 'src/**/demo.js' |
| 383 | ], {base: 'src'}) |
| 384 | .pipe($.if('*.scss', $.sass({ |
| 385 | precision: 10, |
| 386 | onError: console.error.bind(console, 'Sass error:') |
| 387 | }))) |
| 388 | .pipe($.cssInlineImages({webRoot: 'src'})) |
| 389 | .pipe($.if('*.css', $.autoprefixer(AUTOPREFIXER_BROWSERS))) |
| 390 | .pipe(gulp.dest('dist/components')); |
| 391 | }); |
| 392 | |
| 393 | /** |
| 394 | * Generates demo files for testing made of all the snippets and the demo file |
| 395 | * put together. |
| 396 | */ |
| 397 | gulp.task('demos', ['demoresources'], () => { |
| 398 | /** |
| 399 | * Retrieves the list of component folders. |
| 400 | */ |
| 401 | function getComponentFolders() { |
| 402 | return fs.readdirSync('src') |
| 403 | .filter(file => fs.statSync(path.join('src', file)).isDirectory()); |
| 404 | } |
| 405 | |
| 406 | const tasks = getComponentFolders().map(component => { |
| 407 | return gulp.src([ |
| 408 | path.join('src', component, 'snippets', '*.html'), |
| 409 | path.join('src', component, 'demo.html') |
| 410 | ]) |
| 411 | .pipe($.concat('/demo.html')) |
| 412 | // Add basic front matter. |
| 413 | .pipe($.header('---\nlayout: demo\nbodyclass: demo\ninclude_prefix: ../../\n---\n\n')) |
| 414 | .pipe($.frontMatter({ |
| 415 | property: 'page', |
| 416 | remove: true |
| 417 | })) |
| 418 | .pipe($.marked()) |
| 419 | .pipe((() => { |
| 420 | return through.obj((file, enc, cb) => { |
| 421 | file.page.component = component; |
| 422 | cb(null, file); |
| 423 | }); |
| 424 | })()) |
| 425 | .pipe(applyTemplate()) |
| 426 | .pipe(gulp.dest(path.join('dist', 'components', component))); |
| 427 | }); |
| 428 | |
| 429 | return mergeStream(tasks); |
| 430 | }); |
| 431 | |
| 432 | /** |
| 433 | * Generates an HTML file for each md file in _pages directory. |
| 434 | */ |
| 435 | gulp.task('pages', ['components'], () => { |
| 436 | return gulp.src('docs/_pages/*.md') |
| 437 | .pipe($.frontMatter({ |
| 438 | property: 'page', |
| 439 | remove: true |
| 440 | })) |
| 441 | .pipe($.marked()) |
| 442 | .pipe(applyTemplate()) |
| 443 | .pipe($.replace('$$version$$', pkg.version)) |
| 444 | .pipe($.replace('$$hosted_libs_prefix$$', hostedLibsUrlPrefix)) |
| 445 | .pipe($.replace('$$template_archive_prefix$$', templateArchivePrefix)) |
| 446 | /* Replacing code blocks class name to match Prism's. */ |
| 447 | .pipe($.replace('class="lang-', 'class="language-')) |
| 448 | /* Translate html code blocks to "markup" because that's what Prism uses. */ |
| 449 | .pipe($.replace('class="language-html', 'class="language-markup')) |
| 450 | .pipe($.rename(path => { |
| 451 | if (path.basename !== 'index') { |
| 452 | path.dirname = path.basename; |
| 453 | path.basename = 'index'; |
| 454 | } |
| 455 | })) |
| 456 | .pipe(gulp.dest('dist')); |
| 457 | }); |
| 458 | |
| 459 | /** |
| 460 | * Copies assets from MDL and _assets directory. |
| 461 | */ |
| 462 | gulp.task('assets', () => { |
| 463 | return gulp.src([ |
| 464 | 'docs/_assets/**/*', |
| 465 | 'node_modules/clippy/build/clippy.swf', |
| 466 | 'node_modules/swfobject-npm/swfobject/src/swfobject.js', |
| 467 | 'node_modules/prismjs/prism.js', |
| 468 | 'node_modules/prismjs/components/prism-markup.min.js', |
| 469 | 'node_modules/prismjs/components/prism-javascript.min.js', |
| 470 | 'node_modules/prismjs/components/prism-css.min.js', |
| 471 | 'node_modules/prismjs/components/prism-bash.min.js', |
| 472 | 'node_modules/prismjs/dist/prism-default/prism-default.css' |
| 473 | ]) |
| 474 | .pipe($.if(/\.js/i, $.replace('$$version$$', pkg.version))) |
| 475 | .pipe($.if(/\.js/i, $.replace('$$hosted_libs_prefix$$', hostedLibsUrlPrefix))) |
| 476 | .pipe($.if(/\.(svg|jpg|png)$/i, $.imagemin({ |
| 477 | progressive: true, |
| 478 | interlaced: true |
| 479 | }))) |
| 480 | .pipe($.if(/\.css/i, $.autoprefixer(AUTOPREFIXER_BROWSERS))) |
| 481 | .pipe($.if(/\.css/i, $.csso())) |
| 482 | .pipe($.if(/\.js/i, $.uglify({ |
| 483 | preserveComments: 'some', |
| 484 | sourceRoot: '.', |
| 485 | sourceMapIncludeSources: true |
| 486 | }))) |
| 487 | .pipe(gulp.dest('dist/assets')); |
| 488 | }); |
| 489 | |
| 490 | /** |
| 491 | * Defines the list of resources to watch for changes. |
| 492 | */ |
| 493 | function watch() { |
| 494 | gulp.watch(['src/**/*.js', '!src/**/README.md'], |
| 495 | ['scripts', 'demos', 'components', reload]); |
| 496 | gulp.watch(['src/**/*.{scss,css}'], |
| 497 | ['styles', 'styles-grid', 'styletemplates', reload]); |
| 498 | gulp.watch(['src/**/*.html'], ['pages', reload]); |
| 499 | gulp.watch(['src/**/*.{svg,png,jpg}'], ['images', reload]); |
| 500 | gulp.watch(['src/**/README.md'], ['pages', reload]); |
| 501 | gulp.watch(['templates/**/*'], ['templates', reload]); |
| 502 | gulp.watch(['docs/**/*'], ['pages', 'assets', reload]); |
| 503 | gulp.watch(['package.json', 'bower.json', 'LICENSE'], ['metadata']); |
| 504 | } |
| 505 | |
| 506 | /** |
| 507 | * Serves the landing page from "out" directory. |
| 508 | */ |
| 509 | gulp.task('serve:browsersync', () => { |
| 510 | browserSync({ |
| 511 | notify: false, |
| 512 | server: { |
| 513 | baseDir: ['dist'] |
| 514 | } |
| 515 | }); |
| 516 | |
| 517 | watch(); |
| 518 | }); |
| 519 | |
| 520 | gulp.task('serve', () => { |
| 521 | $.connect.server({ |
| 522 | root: 'dist', |
| 523 | port: 5000, |
| 524 | livereload: true |
| 525 | }); |
| 526 | |
| 527 | watch(); |
| 528 | |
| 529 | gulp.src('dist/index.html') |
| 530 | .pipe($.open({uri: 'http://localhost:5000'})); |
| 531 | }); |
| 532 | |
| 533 | // Generate release archive containing just JS, CSS, Source Map deps |
| 534 | gulp.task('zip:mdl', () => { |
| 535 | return gulp.src([ |
| 536 | 'dist/material?(.min)@(.js|.css)?(.map)', |
| 537 | 'LICENSE', |
| 538 | 'bower.json', |
| 539 | 'package.json' |
| 540 | ]) |
| 541 | .pipe($.zip('mdl.zip')) |
| 542 | .pipe(gulp.dest('dist')); |
| 543 | }); |
| 544 | |
| 545 | /** |
| 546 | * Returns the list of children directories inside the given directory. |
| 547 | * @param {string} dir the parent directory |
| 548 | * @return {Array<string>} list of child directories |
| 549 | */ |
| 550 | function getSubDirectories(dir) { |
| 551 | return fs.readdirSync(dir) |
| 552 | .filter(file => fs.statSync(path.join(dir, file)).isDirectory()); |
| 553 | } |
| 554 | |
| 555 | // Generate release archives containing the templates and assets for templates. |
| 556 | gulp.task('zip:templates', () => { |
| 557 | const templates = getSubDirectories('dist/templates'); |
| 558 | |
| 559 | // Generate a zip file for each template. |
| 560 | const generateZips = templates.map(template => { |
| 561 | return gulp.src([ |
| 562 | `dist/templates/${template}/**/*.*`, |
| 563 | 'LICENSE' |
| 564 | ]) |
| 565 | .pipe($.rename(path => { |
| 566 | path.dirname = path.dirname.replace(`dist/templates/${template}`, ''); |
| 567 | })) |
| 568 | .pipe($.zip(`${templateArchivePrefix}${template}.zip`)) |
| 569 | .pipe(gulp.dest('dist')); |
| 570 | }); |
| 571 | |
| 572 | return mergeStream(generateZips); |
| 573 | }); |
| 574 | |
| 575 | gulp.task('zip', [ |
| 576 | 'zip:templates', |
| 577 | 'zip:mdl' |
| 578 | ]); |
| 579 | |
| 580 | gulp.task('genCodeFiles', () => { |
| 581 | return gulp.src([ |
| 582 | 'dist/material.*@(js|css)?(.map)', |
| 583 | 'dist/mdl.zip', |
| 584 | `dist/${templateArchivePrefix}*.zip` |
| 585 | ], {read: false}) |
| 586 | .pipe($.tap(file => { |
| 587 | codeFiles += ` dist/${path.basename(file.path)}`; |
| 588 | })); |
| 589 | }); |
| 590 | |
| 591 | // Push the latest version of code resources (CSS+JS) to Google Cloud Storage. |
| 592 | // Public-read objects in GCS are served by a Google provided and supported |
| 593 | // global, high performance caching/content delivery network (CDN) service. |
| 594 | // This task requires gsutil to be installed and configured. |
| 595 | // For info on gsutil: https://cloud.google.com/storage/docs/gsutil. |
| 596 | gulp.task('pushCodeFiles', () => { |
| 597 | const dest = bucketCode; |
| 598 | console.log(`Publishing ${pkg.version} to CDN (${dest})`); |
| 599 | |
| 600 | // Build cache control and gsutil cmd to copy |
| 601 | // each object into a GCS bucket. The dest is a version specific path. |
| 602 | // The gsutil -m option requests parallel copies. |
| 603 | // The gsutil -h option is used to set metadata headers |
| 604 | // (cache control, in this case). |
| 605 | // Code files should NEVER be touched after uploading, therefore |
| 606 | // 30 days caching is a safe value. |
| 607 | const cacheControl = '-h "Cache-Control:public,max-age=2592000"'; |
| 608 | const gsutilCpCmd = 'gsutil -m cp -z js,css,map '; |
| 609 | const gsutilCacheCmd = `gsutil -m setmeta -R ${cacheControl}`; |
| 610 | |
| 611 | // Upload the goodies to a separate GCS bucket with versioning. |
| 612 | // Using a sep bucket avoids the risk of accidentally blowing away |
| 613 | // old versions in the microsite bucket. |
| 614 | return gulp.src('') |
| 615 | .pipe($.shell([ |
| 616 | `${gsutilCpCmd}${codeFiles} ${dest}/${pkg.version}`, |
| 617 | `${gsutilCacheCmd} ${dest}/${pkg.version}` |
| 618 | ])); |
| 619 | }); |
| 620 | |
| 621 | gulp.task('publish:code', cb => { |
| 622 | runSequence( |
| 623 | ['zip:mdl', 'zip:templates'], |
| 624 | 'genCodeFiles', |
| 625 | 'pushCodeFiles', |
| 626 | cb); |
| 627 | }); |
| 628 | |
| 629 | /** |
| 630 | * Function to publish staging or prod version from local tree, |
| 631 | * or to promote staging to prod, per passed arg. |
| 632 | * @param {string} pubScope the scope to publish to. |
| 633 | */ |
| 634 | function mdlPublish(pubScope) { |
| 635 | let cacheTtl = null; |
| 636 | let src = null; |
| 637 | let dest = null; |
| 638 | |
| 639 | if (pubScope === 'staging') { |
| 640 | // Set staging specific vars here. |
| 641 | cacheTtl = 0; |
| 642 | src = 'dist/*'; |
| 643 | dest = bucketStaging; |
| 644 | } else if (pubScope === 'prod') { |
| 645 | // Set prod specific vars here. |
| 646 | cacheTtl = 60; |
| 647 | src = 'dist/*'; |
| 648 | dest = bucketProd; |
| 649 | } else if (pubScope === 'promote') { |
| 650 | // Set promote (essentially prod) specific vars here. |
| 651 | cacheTtl = 60; |
| 652 | src = `${bucketStaging}/*`; |
| 653 | dest = bucketProd; |
| 654 | } |
| 655 | |
| 656 | let infoMsg = `Publishing ${pubScope}/${pkg.version} to GCS (${dest})`; |
| 657 | if (src) { |
| 658 | infoMsg += ` from ${src}`; |
| 659 | } |
| 660 | console.log(infoMsg); |
| 661 | |
| 662 | // Build gsutil commands: |
| 663 | // The gsutil -h option is used to set metadata headers. |
| 664 | // The gsutil -m option requests parallel copies. |
| 665 | // The gsutil -R option is used for recursive file copy. |
| 666 | const cacheControl = `-h "Cache-Control:public,max-age=${cacheTtl}"`; |
| 667 | const gsutilCacheCmd = `gsutil -m setmeta ${cacheControl} ${dest}/**`; |
| 668 | const gsutilCpCmd = `gsutil -m cp -r -z html,css,js,svg ${src} ${dest}`; |
| 669 | |
| 670 | gulp.src('').pipe($.shell([gsutilCpCmd, gsutilCacheCmd])); |
| 671 | } |
| 672 | |
| 673 | // Push the local build of the MDL microsite and release artifacts to the |
| 674 | // production Google Cloud Storage bucket for general serving to the web. |
| 675 | // Public-read objects in GCS are served by a Google provided and supported |
| 676 | // global, high performance caching/content delivery network (CDN) service. |
| 677 | // This task requires gsutil to be installed and configured. |
| 678 | // For info on gsutil: https://cloud.google.com/storage/docs/gsutil. |
| 679 | // |
| 680 | gulp.task('publish:prod', () => { |
| 681 | mdlPublish('prod'); |
| 682 | }); |
| 683 | |
| 684 | // Promote the staging version of the MDL microsite and release artifacts |
| 685 | // to the production Google Cloud Storage bucket for general serving. |
| 686 | // Public-read objects in GCS are served by a Google provided and supported |
| 687 | // global, high performance caching/content delivery network (CDN) service. |
| 688 | // This task requires gsutil to be installed and configured. |
| 689 | // For info on gsutil: https://cloud.google.com/storage/docs/gsutil. |
| 690 | // |
| 691 | gulp.task('publish:promote', () => { |
| 692 | mdlPublish('promote'); |
| 693 | }); |
| 694 | |
| 695 | // Push the staged version of the MDL microsite and release artifacts |
| 696 | // to a production Google Cloud Storage bucket for staging and pre-production testing. |
| 697 | // |
| 698 | // This task requires gsutil to be installed and configured. |
| 699 | // For info on gsutil: https://cloud.google.com/storage/docs/gsutil. |
| 700 | // |
| 701 | gulp.task('publish:staging', () => { |
| 702 | mdlPublish('staging'); |
| 703 | }); |
| 704 | |
| 705 | gulp.task('_release', () => { |
| 706 | return gulp.src([ |
| 707 | 'dist/material?(.min)@(.js|.css)?(.map)', |
| 708 | 'LICENSE', |
| 709 | 'README.md', |
| 710 | 'bower.json', |
| 711 | 'package.json', |
| 712 | '.jscsrc', |
| 713 | '.jshintrc', |
| 714 | './sr?/**/*', |
| 715 | 'gulpfile.babel.js', |
| 716 | './util?/**/*' |
| 717 | ]) |
| 718 | .pipe(gulp.dest('_release')); |
| 719 | }); |
| 720 | |
| 721 | gulp.task('publish:release', ['_release'], () => { |
| 722 | return gulp.src('_release') |
| 723 | .pipe($.subtree({ |
| 724 | remote: 'origin', |
| 725 | branch: 'release' |
| 726 | })) |
| 727 | .pipe(vinylPaths(del)); |
| 728 | }); |
| 729 | |
| 730 | gulp.task('templates:styles', () => { |
| 731 | return gulp.src('templates/**/*.css') |
| 732 | .pipe($.autoprefixer(AUTOPREFIXER_BROWSERS)) |
| 733 | // FIXME: This crashes. It's a bug in gulp-csso, |
| 734 | // not csso itself. |
| 735 | //.pipe($.csso()) |
| 736 | .pipe(gulp.dest('dist/templates')); |
| 737 | }); |
| 738 | |
| 739 | gulp.task('templates:static', () => { |
| 740 | return gulp.src('templates/**/*.html') |
| 741 | .pipe($.replace('$$version$$', pkg.version)) |
| 742 | .pipe($.replace('$$hosted_libs_prefix$$', hostedLibsUrlPrefix)) |
| 743 | .pipe(gulp.dest('dist/templates')); |
| 744 | }); |
| 745 | |
| 746 | // This task can be used if you want to test the templates against locally |
| 747 | // built version of the MDL libraries. |
| 748 | gulp.task('templates:localtestingoverride', () => { |
| 749 | return gulp.src('templates/**/*.html') |
| 750 | .pipe($.replace('$$version$$', '.')) |
| 751 | .pipe($.replace('$$hosted_libs_prefix$$', '')) |
| 752 | .pipe(gulp.dest('dist/templates')); |
| 753 | }); |
| 754 | |
| 755 | gulp.task('templates:images', () => { |
| 756 | return gulp.src('templates/*/images/**/*') |
| 757 | .pipe($.imagemin({ |
| 758 | progressive: true, |
| 759 | interlaced: true |
| 760 | })) |
| 761 | .pipe(gulp.dest('dist/templates')); |
| 762 | }); |
| 763 | |
| 764 | gulp.task('templates:fonts', () => { |
| 765 | return gulp.src('templates/*/fonts/**/*') |
| 766 | .pipe(gulp.dest('dist/templates/')); |
| 767 | }); |
| 768 | |
| 769 | gulp.task('templates', [ |
| 770 | 'templates:static', |
| 771 | 'templates:images', |
| 772 | 'templates:fonts', |
| 773 | 'templates:styles' |
| 774 | ]); |
| 775 | |
| 776 | gulp.task('styles:gen', ['styles'], () => { |
| 777 | const MaterialCustomizer = require('./docs/_assets/customizer.js'); |
| 778 | const templatePath = path.join(__dirname, 'dist', 'material.min.css.template'); |
| 779 | // TODO: This task needs refactoring once we turn MaterialCustomizer |
| 780 | // into a proper Node module. |
| 781 | const mc = new MaterialCustomizer(); |
| 782 | mc.template = fs.readFileSync(templatePath).toString(); |
| 783 | |
| 784 | let stream = gulp.src(''); |
| 785 | |
| 786 | mc.paletteIndices.forEach(primary => { |
| 787 | mc.paletteIndices.forEach(accent => { |
| 788 | if (primary === accent) { |
| 789 | return; |
| 790 | } |
| 791 | |
| 792 | if (mc.forbiddenAccents.indexOf(accent) !== -1) { |
| 793 | return; |
| 794 | } |
| 795 | |
| 796 | const primaryName = primary.toLowerCase().replace(' ', '_'); |
| 797 | const accentName = accent.toLowerCase().replace(' ', '_'); |
| 798 | |
| 799 | stream = stream.pipe($.file( |
| 800 | `material.${primaryName}-${accentName}.min.css`, |
| 801 | mc.processTemplate(primary, accent) |
| 802 | )); |
| 803 | }); |
| 804 | }); |
| 805 | |
| 806 | stream.pipe(gulp.dest('dist')); |
| 807 | }); |