├── .npmignore ├── .npmrc ├── docs ├── troubleshooting.md ├── banner.png ├── features │ ├── browser-sync.md │ ├── webpack.md │ ├── js.md │ ├── copy.md │ ├── drupal-drush.md │ ├── icons.md │ ├── css.md │ ├── sprite.md │ └── pattern-lab.md ├── extra.css ├── index.md └── usage.md ├── .github ├── CODEOWNERS ├── ISSUE_TEMPLATE.md └── PULL_REQUEST_TEMPLATE.md ├── .eslintignore ├── .travis.yml ├── banner.png ├── .gitignore ├── .editorconfig ├── AUTHORS ├── mkdocs.yml ├── CONTRIBUTORS ├── templates ├── .stylelintrc.json ├── .eslintrc.json ├── gulpfile.js ├── webpack.config.js └── _icons.scss ├── lib ├── drupal.js ├── copy.js ├── core.js ├── browser-sync.js ├── sprite.js ├── webpack.js ├── js.js ├── icons.js ├── css.js └── pattern-lab--php-twig.js ├── .eslintrc.js ├── LICENSE ├── README.md ├── package.json ├── index.js ├── CONTRIBUTING.md └── gulpfile.default.yml /.npmignore: -------------------------------------------------------------------------------- 1 | tests 2 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false 2 | -------------------------------------------------------------------------------- /docs/troubleshooting.md: -------------------------------------------------------------------------------- 1 | --- 2 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @EvanLovely @blaryjp 2 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules/**/* 2 | /templates 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 8 4 | - 7 5 | - 6 6 | -------------------------------------------------------------------------------- /banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ovh/gulp-drupal-stack/HEAD/banner.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | 3 | tests/css/dest/ 4 | 5 | .idea 6 | npm-debug.log 7 | -------------------------------------------------------------------------------- /docs/banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ovh/gulp-drupal-stack/HEAD/docs/banner.png -------------------------------------------------------------------------------- /docs/features/browser-sync.md: -------------------------------------------------------------------------------- 1 | Create a connection between your desk and your website, using Browsersync. 2 | 3 | ## Commands 4 | 5 | - `gulp serve` - Launch Browsersync server 6 | 7 | --- 8 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # Drupal editor configuration normalization 2 | # @see http://editorconfig.org/ 3 | 4 | # This is the top-most .editorconfig file; do not search in parent directories. 5 | root = true 6 | 7 | # All files. 8 | [*] 9 | end_of_line = LF 10 | indent_style = space 11 | indent_size = 2 12 | charset = utf-8 13 | trim_trailing_whitespace = true 14 | insert_final_newline = true 15 | 16 | [composer.{json,lock}] 17 | indent_size = 4 18 | -------------------------------------------------------------------------------- /docs/features/webpack.md: -------------------------------------------------------------------------------- 1 | Manage your JS files using webpack. 2 | 3 | ## Usage 4 | 5 | Copy the `webpack.config.js` example file in your project. 6 | You can then configure the JS bundles that you want: 7 | ```js 8 | entry: { 9 | main: path.resolve(__dirname, './js/main.js') 10 | [...] 11 | }, 12 | ``` 13 | 14 | ## Commands 15 | 16 | - `gulp webpack` - Launch webpack compilation 17 | - `gulp validate:webpack` - Test JS with ESLINT 18 | - `gulp watch:webpack` - Watch and compile 19 | 20 | --- 21 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | # This is the official list of gulp-drupal-stack authors for copyright purposes. 2 | # This file is distinct from the CONTRIBUTORS files 3 | # and it lists the copyright holders only. 4 | 5 | # Names should be added to this file as one of 6 | # Organization's name 7 | # Individual's name 8 | # Individual's name 9 | # See CONTRIBUTORS for the meaning of multiple email addresses. 10 | 11 | # Please keep the list sorted. 12 | 13 | OVH SAS 14 | -------------------------------------------------------------------------------- /docs/features/js.md: -------------------------------------------------------------------------------- 1 | Compiles JS files using Babel. You can optionaly concat, uglify, and add sourcemaps. 2 | 3 | !!! note "" 4 | We recommand to use [webpack](features/webpack.md) for big SPA. 5 | 6 | ## Commands 7 | 8 | - `gulp js` - Compile all JS files using Babel 9 | - `gulp watch:js` - Watch and compile 10 | - `gulp validate:js` - Test JS with ESLINT 11 | - `gulp js:bundleBower` - (optional) compile, uglify, concat bower dependencies (result files will be `bower--*deps*.js`) 12 | - `gulp watch:bower` - (optional) Watch and compile bower deps 13 | 14 | --- 15 | -------------------------------------------------------------------------------- /mkdocs.yml: -------------------------------------------------------------------------------- 1 | site_name: Gulp Drupal Stack 2 | docs_dir: docs 3 | 4 | pages: 5 | - Home: index.md 6 | - Usage: usage.md 7 | - Features: 8 | - CSS: features/css.md 9 | - JS: features/js.md 10 | - Webpack: features/webpack.md 11 | - Icons: features/icons.md 12 | - Sprite: features/sprite.md 13 | - Drupal-Drush: features/drupal-drush.md 14 | - Browsersync: features/browser-sync.md 15 | - Copy: features/copy.md 16 | - Troubleshooting: troubleshooting.md 17 | 18 | markdown_extensions: 19 | - toc: 20 | permalink: True 21 | - admonition: 22 | 23 | extra_css: 24 | - extra.css 25 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### Description 2 | 3 | 4 | 5 | 6 | ### Steps to Reproduce 7 | 8 | 9 | 1. [First Step] 10 | 2. [Second Step] 11 | 3. [and so on...] 12 | 13 | **Expected behavior:** 14 | 15 | 16 | 17 | **Actual behavior:** 18 | 19 | 20 | 21 | **Reproduces how often:** 22 | 23 | 24 | ### Additional Information 25 | 26 | 27 | -------------------------------------------------------------------------------- /CONTRIBUTORS: -------------------------------------------------------------------------------- 1 | # This is the official list of people who can contribute 2 | # (and typically have contributed) code to the gulp-drupal-stack repository. 3 | # 4 | # Names should be added to this file only after verifying that 5 | # the individual or the individual's organization has agreed to 6 | # the appropriate CONTRIBUTING.md file. 7 | # 8 | # Names should be added to this file like so: 9 | # Individual's name 10 | # Individual's name 11 | # 12 | # Please keep the list sorted. 13 | # 14 | 15 | Antoine Leblanc 16 | Xavier Ternisien 17 | -------------------------------------------------------------------------------- /templates/.stylelintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | "stylelint-scss" 4 | ], 5 | "ignoreFiles": [], 6 | "rules": { 7 | "declaration-colon-space-after": "always", 8 | "declaration-no-important": true, 9 | "indentation": 2, 10 | "max-nesting-depth": 5, 11 | "selector-max-specificity": "0,5,5", 12 | "scss/at-extend-no-missing-placeholder": null, 13 | "scss/selector-no-redundant-nesting-selector": true, 14 | "at-rule-no-vendor-prefix": true, 15 | "media-feature-name-no-vendor-prefix": true, 16 | "property-no-vendor-prefix": true, 17 | "selector-no-vendor-prefix": true, 18 | "value-no-vendor-prefix": true 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /docs/features/copy.md: -------------------------------------------------------------------------------- 1 | Copy any files to a destination folder (useful for node_modules vendoring). 2 | 3 | ## Usage 4 | 5 | You simply put the files (input) that you want to copy to output (dist). 6 | ```yml 7 | copy: 8 | enabled: true 9 | files: 10 | - src: "node_modules/lodash/dist/lodash.js" 11 | dest: "dist/vendors/" 12 | - src: "node_modules/underscore/dist/*.js" 13 | dest: "dist/vendors/" 14 | concat: true # (optional) enable concat 15 | destName: "underscore.js" # (optional) concatened file name 16 | [...] 17 | ``` 18 | 19 | ## Commands 20 | 21 | - `gulp copy` - Launch copy task 22 | - `gulp watch:copy` - Watch and copy 23 | 24 | --- 25 | -------------------------------------------------------------------------------- /docs/features/drupal-drush.md: -------------------------------------------------------------------------------- 1 | Launch a Drupal command. 2 | 3 | ## Commands 4 | 5 | - `gulp cr` - Launch Drupal command (by default `drush cr`) 6 | - `gulp watch:drupal` - Watch files and launch `gulp cr` 7 | 8 | ## Config 9 | 10 | - `config.drupal.themeFile` - The filename of your YOUR_THEME.info.yml (required for PatternLab tasks) 11 | - `config.drupal.dir` - The root directory for Drush 12 | - `config.drupal.command` - The drush command to execute 13 | 14 | ## Notes 15 | 16 | If you want to use it with [Drucker](https://github.com/ovh/drucker), you need to: 17 | 18 | - set the `config.drupal.dir` to `./path/to/drucker` 19 | - set the `config.drupal.command` to `. load-env && drush cr` 20 | 21 | --- 22 | -------------------------------------------------------------------------------- /lib/drupal.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const core = require('./core'); 4 | const browserSync = require('browser-sync'); 5 | 6 | module.exports = (gulp, config, tasks) => { 7 | function clearDrupalCache(done) { 8 | core.sh(`cd ${config.drupal.dir} && ${config.drupal.command}`, true, () => { 9 | if (config.browserSync.enabled) { 10 | browserSync.get('server').reload(); 11 | } 12 | done(); 13 | }); 14 | } 15 | 16 | clearDrupalCache.description = 'Clear Drupal Cache'; 17 | 18 | gulp.task('cr', clearDrupalCache); 19 | 20 | gulp.task('watch:drupal', () => { 21 | gulp.watch(config.drupal.watch, clearDrupalCache); 22 | }); 23 | tasks.watch.push('watch:drupal'); 24 | }; 25 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | // rule reference: http://eslint.org/docs/rules 2 | // individual rule reference: http://eslint.org/docs/rules/NAME-OF-RULE 3 | module.exports = { 4 | extends: 'airbnb-base', 5 | env: { 6 | es6: true, 7 | node: true, 8 | }, 9 | rules: { 10 | strict: [0], 11 | 'prefer-spread': [0], 12 | 'no-plusplus': ['error', { allowForLoopAfterthoughts: true }], 13 | 'prefer-arrow-callback': ['error', { allowNamedFunctions: true }], 14 | 'no-console': [0], 15 | 'global-require': [0], 16 | 'import/no-dynamic-require': [0], 17 | 'comma-dangle': ['error', { 18 | arrays: 'never', 19 | objects: 'never', 20 | imports: 'never', 21 | exports: 'never', 22 | functions: 'never' 23 | }], 24 | 'prefer-destructuring': [0] 25 | }, 26 | }; 27 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### Requirements 2 | 3 | * Filling out the template is required. Any pull request that does not include enough information to be reviewed in a timely manner may be closed at the maintainers' discretion. 4 | * All new code requires tests to ensure against regressions 5 | 6 | ## Title of the Pull Requests 7 | 8 | 9 | ### Description of the Change 10 | 11 | 12 | 13 | 14 | ### Benefits 15 | 16 | 17 | 18 | 19 | ### Possible Drawbacks 20 | 21 | 22 | 23 | 24 | ### Applicable Issues 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /docs/extra.css: -------------------------------------------------------------------------------- 1 | p + ul, 2 | p + ol { 3 | margin-top: -20px !important; 4 | margin-left: 15px; 5 | } 6 | .admonition > *:last-child { 7 | margin-bottom: 0 !important; 8 | } 9 | 10 | .admonition.quote, 11 | .admonition.success { 12 | padding: 12px; 13 | margin-bottom: 24px; 14 | line-height: 24px; 15 | } 16 | .admonition.quote { 17 | background: hsla(0,0%,93%,.5); 18 | } 19 | .admonition.success { 20 | background: rgba(0,200,83,.1); 21 | } 22 | 23 | .admonition.note { 24 | border-left: .2rem solid #00b0ff !important; 25 | } 26 | .admonition.success { 27 | border-left: .2rem solid #00c853 !important; 28 | } 29 | .admonition.danger { 30 | border-left: .2rem solid #ff1744 !important; 31 | } 32 | .admonition.warning { 33 | border-left: .2rem solid #ff9100 !important; 34 | } 35 | .admonition.cite { 36 | border-left: .2rem solid #9e9e9e; 37 | } 38 | -------------------------------------------------------------------------------- /lib/copy.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const concat = require('gulp-concat'); 4 | const gulpif = require('gulp-if'); 5 | 6 | module.exports = (gulp, config, tasks) => { 7 | function copyCurrentFiles(files) { 8 | return gulp.src(files.src) 9 | .pipe(gulpif(files.concat, concat(files.destName || 'undefined.js'))) 10 | .pipe(gulp.dest(files.dest)); 11 | } 12 | 13 | copyCurrentFiles.description = 'Copies multiple files into a given destination (and optionally concat them).'; 14 | 15 | gulp.task('copy', gulp.parallel(config.copy.files.map(files => function copyFiles() { 16 | return copyCurrentFiles(files); 17 | }))); 18 | 19 | gulp.task('watch:copy', gulp.parallel(config.copy.files.map(files => function watchCopyFiles() { 20 | return gulp.watch(files.src, gulp.series(function copyFiles() { 21 | return copyCurrentFiles(files); 22 | })); 23 | }))); 24 | 25 | tasks.compile.push('copy'); 26 | tasks.watch.push('watch:copy'); 27 | }; 28 | 29 | -------------------------------------------------------------------------------- /docs/features/icons.md: -------------------------------------------------------------------------------- 1 | Uses [gulp-iconfont](https://github.com/nfroidure/gulp-iconfont). Grabs a folder of SVG icons and turns them into font icons, creates a Sass mixin and class for each based on filename, adds all to a demo page. 2 | 3 | ## Usage 4 | 5 | Given a file named `facebook.svg`, you can use this Sass mixin: 6 | 7 | ```scss 8 | @include icon('facebook'); 9 | ``` 10 | 11 | Or this HTML class: 12 | 13 | ```html 14 | 15 | ``` 16 | 17 | ## Commands 18 | 19 | - `gulp icons` - Compile Icons 20 | - `gulp watch:icons` - Watch for icons and compile 21 | 22 | ## Config 23 | 24 | - `config.icons.src` - Array or String of globbed SVG files 25 | - `config.icons.dest` - Destination directory for the fonts 26 | - `config.icons.fontPathPrefix` - Font path prefix 27 | - `config.icons.iconName` - Name of the icon (will be the name of the font) 28 | - `config.icons.classNamePrefix` - Icon class name prefix (default: "icon") 29 | - `config.icons.templates.css.dest` - The generated SCSS file 30 | 31 | --- 32 | -------------------------------------------------------------------------------- /templates/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "eslint-config-airbnb", 3 | "env": { 4 | "browser": true, 5 | "es6": true, 6 | "node": true 7 | }, 8 | "globals": { 9 | "Drupal": true, 10 | "drupalSettings": true, 11 | "drupalTranslations": true, 12 | "domready": true, 13 | "jQuery": true, 14 | "_": true, 15 | "matchMedia": true, 16 | "Backbone": true, 17 | "Modernizr": true, 18 | "CKEDITOR": true 19 | }, 20 | "rules": { 21 | "consistent-return": [0], 22 | "no-underscore-dangle": [0], 23 | "max-nested-callbacks": [1, 3], 24 | "import/no-mutable-exports": [1], 25 | "no-plusplus": [1, { 26 | "allowForLoopAfterthoughts": true 27 | }], 28 | "no-param-reassign": [0], 29 | "no-prototype-builtins": [0], 30 | "valid-jsdoc": [1, { 31 | "prefer": { 32 | "returns": "return", 33 | "property": "prop" 34 | }, 35 | "requireReturn": false 36 | }], 37 | "brace-style": ["error", "stroustrup"], 38 | "no-unused-vars": [1] 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2013-present OVH SAS 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /templates/gulpfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const gulp = require('gulp'); 4 | const yaml = require('js-yaml'); 5 | const fs = require('fs'); 6 | 7 | // `rc` allows all config options to be overridden with CLI flags like `--js.enabled=""` or in `~/.gulp-drupal-corerc` files, among many others: https://www.npmjs.com/package/rc 8 | const config = require('rc')('gulp-drupal-stack', yaml.safeLoad(fs.readFileSync(`${__dirname}/gulpfile.yml`, 'utf8'), { json: true })); 9 | const drupalStack = require('gulp-drupal-stack'); 10 | 11 | const tasks = { 12 | compile: [], 13 | watch: [], 14 | validate: [], 15 | test: [], 16 | clean: [], 17 | 'default': [] 18 | }; 19 | 20 | drupalStack(gulp, config, tasks); 21 | 22 | gulp.task('clean', gulp.parallel(tasks.clean)); 23 | gulp.task('compile', gulp.series( 24 | 'clean', 25 | gulp.series(tasks.compile) 26 | )); 27 | gulp.task('build', gulp.series(['compile'])); // alias 28 | gulp.task('validate', gulp.parallel(tasks.validate)); 29 | gulp.task('test', gulp.parallel(tasks.test)); 30 | gulp.task('watch', gulp.parallel(tasks.watch)); 31 | tasks.default.push('watch'); 32 | gulp.task('default', gulp.series( 33 | 'compile', 34 | gulp.parallel(tasks.default) 35 | )); 36 | -------------------------------------------------------------------------------- /docs/features/css.md: -------------------------------------------------------------------------------- 1 | Compiles SCSS to CSS using [gulp-sass](https://github.com/dlmanning/gulp-sass), which in turn uses `node-sass`, which in turn uses `libsass`. 2 | 3 | ## Usage 4 | 5 | You can create individual CSS files bundle, using the config: 6 | ```yml 7 | src: 8 | - scss/main.scss # -> dist/main.css 9 | - scss/my/app1.scss # -> dist/app1.css 10 | - scss/my/app2.scss # -> dist/app2.css 11 | [...] 12 | ``` 13 | 14 | ## Commands 15 | 16 | - `gulp css` - Compile SCSS to CSS 17 | - `gulp watch:css` - Watch and compile 18 | - `gulp validate:css` - Test SCSS with [gulp-sass-lint](https://github.com/sasstools/gulp-sass-lint), which uses [sass-lint](https://github.com/sasstools/sass-lint) (Pure node.js - no Ruby) 19 | - `gulp format:css` - Format SCSS with [gulp-csscombx](https://github.com/drugan/gulp-csscombx), which uses [csscombx](https://github.com/drugan/csscombx) 20 | 21 | ## Config 22 | 23 | - `config.css.src` - Array of SCSS files 24 | - `config.css.dest` - String of Destination directory for CSS 25 | - `config.css.lint.enabled` - Boolean for if Linting should occur 26 | - `config.css.csscombx.enabled` - Boolean for if Formating should occur 27 | - `config.css.sourceComments` - Boolean - Enable comments written in CSS that shows SCSS source. **Don't turn on permanently**, it's useful if SourceMaps aren't working. 28 | - `config.css.autoPrefixerBrowsers` - Array of [Browsers to Support](https://github.com/ai/browserslist#queries) for Autoprefixer (used by PostCSS). 29 | 30 | --- 31 | -------------------------------------------------------------------------------- /templates/webpack.config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const merge = require('webpack-merge'); 4 | const path = require('path'); 5 | const webpack = require('webpack'); 6 | 7 | // Shared configuration. 8 | const commonConfig = { 9 | context: path.resolve(__dirname, './'), 10 | entry: { 11 | main: path.resolve(__dirname, './js/main.js'), 12 | }, 13 | output: { 14 | path: path.resolve(__dirname, 'dist'), 15 | filename: '[name].js', 16 | }, 17 | externals: { 18 | jquery: 'jQuery', 19 | }, 20 | module: { 21 | rules: [{ 22 | test: /\.js$/, 23 | exclude: /node_modules/, 24 | use: 'babel-loader', 25 | }], 26 | }, 27 | plugins: [], 28 | }; 29 | 30 | // Development configuration. 31 | const developmentConfig = { 32 | devtool: 'cheap-eval-source-map', 33 | plugins: [ 34 | new webpack.DefinePlugin({ 35 | 'process.env.NODE_ENV': JSON.stringify('development'), 36 | }), 37 | ], 38 | }; 39 | 40 | // Production configuration. 41 | const productionConfig = { 42 | devtool: 'source-map', 43 | plugins: [ 44 | new webpack.DefinePlugin({ 45 | 'process.env.NODE_ENV': JSON.stringify('production'), 46 | }), 47 | new webpack.LoaderOptionsPlugin({ 48 | minimize: true, 49 | debug: false, 50 | }), 51 | new webpack.optimize.UglifyJsPlugin({ 52 | sourceMap: 'source-map', 53 | }), 54 | ], 55 | }; 56 | 57 | // Export config based on the current environment. 58 | if (process.env.NODE_ENV === 'production') { 59 | module.exports = merge(commonConfig, productionConfig); 60 | } else { 61 | module.exports = merge(commonConfig, developmentConfig); 62 | } 63 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Gulp Drupal Stack 2 | ================= 3 | 4 | ![gulp-drupal-stack-banner](banner.png) 5 | 6 | [![travis](https://travis-ci.org/ovh/gulp-drupal-stack.svg?branch=master)](https://travis-ci.org/ovh/gulp-drupal-stack) 7 | [![Documentation Status](https://readthedocs.org/projects/gulp-drupal-stack/badge/?version=latest)](http://gulp-drupal-stack.readthedocs.io/en/latest/?badge=latest) 8 | 9 | 10 | This stack core is to be included in your main project and sets up many Gulp tasks that can work in many flexible ways by passing in different `config` objects, which can be based off of `gulpfile.default.yml` (and is merged with). 11 | 12 | 13 | ## Features 14 | 15 | - SCSS => CSS compiling with LibSass, PostCSS, linting, CSScomb(x), and SourceMaps 16 | - JS compiling via Babel, linting and aggregation 17 | - webpack module bundling 18 | - SVG => Font Icons compiling with support for adding mixins and classes to SCSS along with a demo page 19 | - Drupal file watching to trigger Drush cache clears 20 | - Copy any files to an other location 21 | - Sprite generator (with Retina Display support) 22 | 23 | All is easily configurable by changing values in your `gulpfile.yml` file in your project. These values are merged into the `gulpfile.default.yml` file - look there for the available options and defaults. 24 | 25 | 26 | ## Documentation 27 | 28 | Full documentation is available [here](https://gulp-drupal-stack.readthedocs.io/en/latest/). 29 | 30 | 31 | #### TODO 32 | 33 | - Browsersync live reload and style injection (should be OK, not tested) 34 | - Images => Images optimization (to validate) 35 | - JS specs => JS tests using Karma 36 | 37 | 38 | ## Contributing 39 | 40 | Have a look at the [Contributing section](.github/CONTRIBUTING.md). 41 | 42 | 43 | ## Credits 44 | 45 | Original project from [`p2-theme-core`](https://github.com/phase2/p2-theme-core). 46 | 47 | 48 | ## License 49 | 50 | MIT (original license) 51 | -------------------------------------------------------------------------------- /docs/features/sprite.md: -------------------------------------------------------------------------------- 1 | Uses [gulp.spritesmith](https://github.com/twolfson/gulp.spritesmith). Grabs a folder of images and turns them into a sprite, creates a Sass mixin and class for each based on filename. 2 | 3 | A documentation for SCSS features is available [here](https://www.bignerdranch.com/blog/css-sprite-management-with-gulp-part2/). 4 | 5 | ## Usage 6 | 7 | Given a file named `facebook.png`, you can use this Sass mixin: 8 | 9 | ```scss 10 | @include sprite('sprite-facebook'); 11 | ``` 12 | 13 | Or this HTML class: 14 | 15 | ```html 16 | 17 | ``` 18 | 19 | ### Retina Display support 20 | 21 | You can generate a second sprite for Retina Display. 22 | First, you need to duplicates all your images and append "@2x" in the filename. For example: "facebook.png" and "facebook@2x.png". 23 | 24 | After enabled it, you can now use the mixin: 25 | 26 | ```scss 27 | @include retina-sprite('sprite-facebook'); 28 | ``` 29 | 30 | It will automatically take sprite@2x for Retina Display, and normal sprite for others. 31 | 32 | 33 | ## Commands 34 | 35 | - `gulp sprite` - Generates the sprite 36 | - `gulp watch:sprite` - Watch for images modifed and regenerate the sprite 37 | 38 | ## Config 39 | 40 | - `config.sprite.src` - Array or String of globbed PNG files 41 | - `config.sprite.imgDest` - Destination directory for the sprite image file 42 | - `config.sprite.cssDest` - Destination directory for the sprite SCSS file 43 | - `config.sprite.imgName` - Name of the sprite image file 44 | - `config.sprite.cssName` - Name of the sprite SCSS file 45 | - `config.sprite.imgPathPrefix` - Sprite image path prefix 46 | - `config.sprite.spritesheetName` - Name of the sprite 47 | - `config.sprite.imagemin` - Enable imagemin compression for the sprite image file 48 | - `config.sprite.retina.enabled` - Enable retina sprite generation 49 | - `config.sprite.retina.imgName` - Name of the retina sprite image file 50 | - `config.sprite.retina.filter` - Images filter that match the retina files 51 | 52 | --- 53 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | Gulp Drupal Stack 2 | ================= 3 | 4 | ![gulp-drupal-stack-banner](banner.png) 5 | 6 | This stack core is to be included in your main project and sets up many Gulp tasks that can work in many flexible ways by passing in different `config` objects, which can be based off of `gulpfile.default.yml` (and is merged with). 7 | 8 | 9 | ## Features 10 | 11 | - SCSS => CSS compiling with LibSass, PostCSS, linting, CSScomb(x), and SourceMaps 12 | - JS compiling via Babel, linting and aggregation 13 | - Pattern Lab Twig compiling & BrowserSync live reload and style injection 14 | - webpack module bundling 15 | - SVG => Font Icons compiling with support for adding mixins and classes to SCSS along with a demo page 16 | - Drupal file watching to trigger Drush cache clears 17 | - Copy any files to an other location 18 | - Sprite generator (with Retina Display support) 19 | 20 | All is easily configurable by changing values in your `gulpfile.yml` file in your project. These values are merged into the `gulpfile.default.yml` file - look there for the available options and defaults. 21 | 22 | 23 | ## Prerequisites 24 | 25 | - [Node](https://nodejs.org) 26 | - [Gulp-cli](http://gulpjs.com/): `npm install -g gulp-cli` 27 | 28 | 29 | ## Installation 30 | 31 | Follow theses steps: 32 | 33 | ```bash 34 | $ cd 35 | # (optional) init a new npm module 36 | $ npm init 37 | # Install it 38 | $ npm install gulp-drupal-stack --save-dev 39 | # Create a gulpfile.js 40 | $ cp node_modules/gulp-drupal-stack/templates/gulpfile.js ./ 41 | # Create a gulpfile.yml (config file) 42 | $ vi gulpfile.yml 43 | # 44 | ``` 45 | 46 | ### IDE/Text Editor Setup 47 | 48 | - Install an EditorConfig plugin 49 | - Ignore the indexing of these directories: 50 | - `node_modules/` 51 | - `bower_components/` 52 | - `dest/` 53 | - `pattern-lab/public/` 54 | - `pattern-lab/vendor/` 55 | 56 | 57 | ## Usage 58 | 59 | See [Usage](usage.md) section. 60 | 61 | 62 | ## Credits 63 | 64 | Original project from [p2-theme-core](https://github.com/phase2/p2-theme-core). 65 | 66 | 67 | ## License 68 | 69 | MIT (original license) 70 | 71 | --- 72 | -------------------------------------------------------------------------------- /lib/core.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const exec = require('child_process').exec; 4 | const notifier = require('node-notifier'); 5 | 6 | function sh(cmd, exitOnError, cb) { 7 | const child = exec(cmd, { 8 | encoding: 'utf8', 9 | timeout: 1000 * 60 * 3 // 3 min; just want to make sure nothing gets detached forever. 10 | }); 11 | let stdout = ''; 12 | child.stdout.on('data', (data) => { 13 | stdout += data; 14 | process.stdout.write(data); 15 | }); 16 | child.stderr.on('data', (data) => { 17 | process.stdout.write(data); 18 | }); 19 | child.on('close', (code) => { 20 | if (code > 0) { 21 | if (exitOnError) { 22 | if (typeof cb === 'function') { 23 | cb(new Error(`Error with code ${code} after running: ${cmd}`)); 24 | } else { 25 | process.stdout.write(`Error with code ${code} after running: ${cmd} \n`); 26 | process.exit(code); 27 | } 28 | } else { 29 | notifier.notify({ 30 | title: cmd, 31 | message: stdout, 32 | sound: true 33 | }); 34 | } 35 | } 36 | if (typeof cb === 'function') cb(); 37 | }); 38 | } 39 | 40 | /** 41 | * Flatten Array 42 | * @param arrayOfArrays {Array[]} 43 | * @returns {Array} 44 | */ 45 | function flattenArray(arrayOfArrays) { 46 | return [].concat.apply([], arrayOfArrays); 47 | } 48 | 49 | /** 50 | * Make an array unique by removing duplicate entries. 51 | * @param item {Array} 52 | * @returns {Array} 53 | */ 54 | function uniqueArray(item) { 55 | const u = {}; 56 | const newArray = []; 57 | for (let i = 0, l = item.length; i < l; ++i) { 58 | if (!{}.hasOwnProperty.call(u, item[i])) { 59 | newArray.push(item[i]); 60 | u[item[i]] = 1; 61 | } 62 | } 63 | return newArray; 64 | } 65 | 66 | /** 67 | * Prepare Error message for `done()` Gulp Task callbacks that do not contain Stack Traces. 68 | * @param {string} message 69 | * @returns {Error} 70 | * @see http://stackoverflow.com/a/39093327/1033782 71 | */ 72 | function error(message) { 73 | const err = new Error(message); 74 | err.showStack = false; 75 | return err; 76 | } 77 | 78 | module.exports = { 79 | sh, 80 | flattenArray, 81 | uniqueArray, 82 | error 83 | }; 84 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gulp-drupal-stack", 3 | "version": "3.3.0", 4 | "description": "OVH Gulp tasks for Drupal themes and modules", 5 | "main": "index.js", 6 | "author": "OVH SAS", 7 | "keywords": [ 8 | "prototyping" 9 | ], 10 | "scripts": { 11 | "test": "eslint index.js lib/**", 12 | "start": "nodemon --watch index.js --watch ./lib --exec 'npm test' ", 13 | "preversion": "npm run test", 14 | "postversion": "git push && git push --tags", 15 | "gulp": "gulp" 16 | }, 17 | "license": "MIT", 18 | "dependencies": { 19 | "autoprefixer": "^8.0.0", 20 | "browser-sync": "^2.23.6", 21 | "clone": "^2.1.1", 22 | "del": "^3.0.0", 23 | "eslint": "^4.17.0", 24 | "eslint-config-airbnb": "^16.1.0", 25 | "eslint-plugin-import": "^2.8.0", 26 | "eslint-plugin-jsx-a11y": "^6.0.3", 27 | "eslint-plugin-react": "^7.6.1", 28 | "glob": "^7.1.2", 29 | "gulp": "^4.0.0", 30 | "gulp-babel": "^7.0.1", 31 | "gulp-cached": "^1.1.1", 32 | "gulp-concat": "^2.6.1", 33 | "gulp-copy": "^1.1.0", 34 | "gulp-csscombx": "^4.2.1", 35 | "gulp-eslint": "^4.0.2", 36 | "gulp-flatten": "^0.4.0", 37 | "gulp-iconfont": "^9.1.0", 38 | "gulp-if": "^2.0.2", 39 | "gulp-imagemin": "^4.1.0", 40 | "gulp-inject": "^4.3.0", 41 | "gulp-notify": "^3.2.0", 42 | "gulp-plumber": "^1.2.0", 43 | "gulp-postcss": "^7.0.1", 44 | "gulp-sass": "^3.1.0", 45 | "gulp-sass-glob": "github:tomgrooffer/gulp-sass-glob#99d06af", 46 | "gulp-sourcemaps": "^2.6.4", 47 | "gulp-stylelint": "^6.0.0", 48 | "gulp-uglify": "^3.0.0", 49 | "gulp.spritesmith": "^6.9.0", 50 | "js-yaml": "^3.10.0", 51 | "lodash": "^4.17.5", 52 | "main-bower-files": "^2.13.1", 53 | "merge-stream": "^1.0.1", 54 | "node-notifier": "^5.2.1", 55 | "sassdoc": "^2.5.0", 56 | "stylelint": "^8.4.0", 57 | "stylelint-scss": "^2.3.0", 58 | "through2": "^2.0.3", 59 | "vinyl-buffer": "^1.0.1", 60 | "webpack": "^3.11.0" 61 | }, 62 | "devDependencies": { 63 | "eslint": "^4.17.0", 64 | "eslint-config-airbnb": "^16.1.0", 65 | "eslint-plugin-import": "^2.8.0", 66 | "gulp-debug": "^3.2.0", 67 | "gulp-util": "^3.0.8", 68 | "nodemon": "^1.14.12" 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /lib/browser-sync.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const browserSync = require('browser-sync').create('server'); 4 | const path = require('path'); 5 | const _ = require('lodash'); 6 | 7 | module.exports = (gulp, config, tasks) => { 8 | const watchFiles = []; 9 | if (config.css.enabled) { 10 | watchFiles.push(path.join(config.css.dest, '*.css')); 11 | } 12 | if (config.browserSync.watchFiles) { 13 | config.browserSync.watchFiles.forEach((file) => { 14 | watchFiles.push(file); 15 | }); 16 | } 17 | const options = { 18 | browser: config.browserSync.browser, 19 | files: watchFiles, 20 | port: config.browserSync.port, 21 | tunnel: config.browserSync.tunnel, 22 | open: config.browserSync.openBrowserAtStart, 23 | reloadOnRestart: true, 24 | reloadDelay: config.browserSync.reloadDelay, 25 | reloadDebounce: config.browserSync.reloadDebounce, 26 | // https://www.browsersync.io/docs/options#option-middleware 27 | middleware: config.browserSync.middleware || [], 28 | // https://www.browsersync.io/docs/options#option-rewriteRules 29 | rewriteRules: config.browserSync.rewriteRules || [], 30 | // placing at `` instead of `` 31 | snippetOptions: { 32 | rule: { 33 | match: /<\/body>/i, 34 | fn: (snippet, match) => snippet + match 35 | } 36 | }, 37 | notify: { 38 | styles: [ 39 | 'display: none', 40 | 'padding: 15px', 41 | 'font-family: sans-serif', 42 | 'position: fixed', 43 | 'font-size: 0.9em', 44 | 'z-index: 9999', 45 | 'bottom: 0px', 46 | 'right: 0px', 47 | 'border-bottom-left-radius: 5px', 48 | 'background-color: #1B2032', 49 | 'margin: 0', 50 | 'color: white', 51 | 'text-align: center' 52 | ] 53 | } 54 | }; 55 | if (config.browserSync.domain) { 56 | _.merge(options, { 57 | proxy: config.browserSync.domain, 58 | startPath: config.browserSync.startPath, 59 | serveStatic: config.browserSync.serveStatic || [] 60 | }); 61 | } else { 62 | _.merge(options, { 63 | server: { 64 | baseDir: config.browserSync.baseDir 65 | }, 66 | startPath: config.browserSync.startPath 67 | }); 68 | } 69 | 70 | function serve() { 71 | return browserSync.init(options); 72 | } 73 | serve.description = 'Create a local server using Browsersync'; 74 | gulp.task('serve', serve); 75 | tasks.default.push('serve'); 76 | }; 77 | -------------------------------------------------------------------------------- /lib/sprite.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const gulpif = require('gulp-if'); 4 | const join = require('path').join; 5 | const del = require('del'); 6 | const spritesmith = require('gulp.spritesmith'); 7 | const buffer = require('vinyl-buffer'); 8 | const imagemin = require('gulp-imagemin'); 9 | const merge = require('merge-stream'); 10 | 11 | module.exports = (gulp, config, tasks) => { 12 | function sprite() { 13 | const spriteData = gulp.src(config.sprite.src) 14 | .pipe(spritesmith({ 15 | imgName: config.sprite.imgName, 16 | imgPath: `${config.sprite.imgPathPrefix}${config.sprite.imgName}`, 17 | cssName: config.sprite.cssName, 18 | cssSpritesheetName: config.sprite.spritesheetName, 19 | retinaSrcFilter: config.sprite.retina.enabled ? config.sprite.retina.filter : undefined, 20 | retinaImgName: config.sprite.retina.enabled ? config.sprite.retina.imgName : undefined, 21 | retinaImgPath: config.sprite.retina.enabled ? `${config.sprite.imgPathPrefix}${config.sprite.retina.imgName}` : undefined, 22 | cssRetinaSpritesheetName: config.sprite.retina.enabled ? `${config.sprite.spritesheetName}-2x` : undefined, 23 | cssVarMap(datas) { 24 | // eslint-disable-next-line no-param-reassign 25 | datas.name = `${config.sprite.spritesheetName}-${datas.name}`; 26 | } 27 | })); 28 | 29 | // Pipe image stream through image optimizer and onto disk 30 | const imgStream = spriteData.img 31 | // DEV: We must buffer our stream into a Buffer for `imagemin` 32 | .pipe(buffer()) 33 | .pipe(gulpif(config.sprite.imagemin, imagemin())) 34 | .pipe(gulp.dest(config.sprite.imgDest)); 35 | 36 | // Pipe CSS stream onto disk 37 | const cssStream = spriteData.css 38 | .pipe(gulp.dest(config.sprite.cssDest)); 39 | 40 | // Return a merged stream to handle both `end` events 41 | return merge(imgStream, cssStream); 42 | } 43 | 44 | sprite.description = 'Generates a sprite (img and scss files).'; 45 | 46 | gulp.task('sprite', sprite); 47 | 48 | gulp.task('clean:sprite', (done) => { 49 | del([ 50 | join(config.sprite.imgDest, config.sprite.imgName), 51 | join(config.sprite.cssDest, config.sprite.cssName) 52 | ], { force: true }).then(() => { 53 | done(); 54 | }); 55 | }); 56 | 57 | gulp.task('watch:sprite', () => { 58 | gulp.watch(config.sprite.src, sprite); 59 | }); 60 | 61 | tasks.watch.push('watch:sprite'); 62 | 63 | tasks.compile.push('sprite'); 64 | 65 | tasks.clean.push('clean:sprite'); 66 | }; 67 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const _ = require('lodash'); 4 | const yaml = require('js-yaml'); 5 | const fs = require('fs'); 6 | 7 | // read default config settings 8 | const defaultConfig = yaml.safeLoad(fs.readFileSync(`${__dirname}/gulpfile.default.yml`, 'utf8'), { json: true }); 9 | 10 | module.exports = (gulp, userConfig, tasks) => { 11 | const config = _.merge(defaultConfig, userConfig); 12 | 13 | /* eslint-disable global-require */ 14 | if (config.browserSync.enabled) { 15 | require('./lib/browser-sync')(gulp, config, tasks); 16 | } 17 | 18 | if (config.icons.enabled) { 19 | require('./lib/icons')(gulp, config, tasks); 20 | } 21 | 22 | if (config.sprite.enabled) { 23 | require('./lib/sprite')(gulp, config, tasks); 24 | } 25 | 26 | if (config.js.enabled) { 27 | require('./lib/js')(gulp, config, tasks); 28 | } 29 | 30 | if (config.css.enabled) { 31 | require('./lib/css')(gulp, config, tasks); 32 | } 33 | 34 | if (config.patternLab.enabled) { 35 | require('./lib/pattern-lab--php-twig')(gulp, config, tasks); 36 | } 37 | 38 | if (config.drupal.enabled) { 39 | require('./lib/drupal')(gulp, config, tasks); 40 | } 41 | 42 | if (config.webpack.enabled) { 43 | require('./lib/webpack')(gulp, config, tasks); 44 | } 45 | 46 | if (config.copy.enabled) { 47 | require('./lib/copy')(gulp, config, tasks); 48 | } 49 | 50 | /* eslint-enable global-require */ 51 | 52 | // This is a fix fo Gulp, because series and paparallel needs at least one task 53 | gulp.task('nothing-to-do', done => done()); 54 | 55 | if (!tasks.clean.length) { 56 | tasks.clean.push('nothing-to-do'); 57 | } 58 | if (!tasks.compile.length) { 59 | tasks.compile.push('nothing-to-do'); 60 | } 61 | if (!tasks.validate.length) { 62 | tasks.validate.push('nothing-to-do'); 63 | } 64 | if (!tasks.test.length) { 65 | tasks.test.push('nothing-to-do'); 66 | } 67 | if (!tasks.watch.length) { 68 | tasks.watch.push('nothing-to-do'); 69 | } 70 | if (!tasks.default.length) { 71 | tasks.default.push('nothing-to-do'); 72 | } 73 | 74 | // Instead of `gulp.parallel`, which is what is set in Pattern Lab Starter's `gulpfile.js`, this 75 | // uses `gulp.series`. Needed to help with the Gulp task dependencies lost going from v3 to v4. 76 | // We basically need icons compiled before CSS & CSS/JS compiled before inject:pl before pl 77 | // compile. The order of the `require`s above is the order that compiles run in; not perfect, but 78 | // it works. 79 | // eslint-disable-next-line no-param-reassign 80 | tasks.compile = gulp.series(tasks.compile); 81 | }; 82 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to gulp-drupal-stack 2 | 3 | This project accepts contributions. In order to contribute, you should 4 | pay attention to a few things: 5 | 6 | 1. your code must follow the coding style rules 7 | 2. your code must be unit-tested 8 | 3. your code must be documented 9 | 4. your work must be signed (see below) 10 | 5. you may contribute through GitHub Pull Requests 11 | 12 | # Submitting Modifications 13 | 14 | The contributions should be submitted through Github Pull Requests 15 | and follow the DCO which is defined below. 16 | 17 | # Licensing for new files 18 | 19 | gulp-drupal-stack is licensed under a MIT license. Anything 20 | contributed to gulp-drupal-stack must be released under this license. 21 | 22 | # Developer Certificate of Origin (DCO) 23 | 24 | To improve tracking of contributions to this project we will use a 25 | process modeled on the modified DCO 1.1 and use a "sign-off" procedure 26 | on patches that are being emailed around or contributed in any other 27 | way. 28 | 29 | The sign-off is a simple line at the end of the explanation for the 30 | patch, which certifies that you wrote it or otherwise have the right 31 | to pass it on as an open-source patch. The rules are pretty simple: 32 | if you can certify the below: 33 | 34 | By making a contribution to this project, I certify that: 35 | 36 | (a) The contribution was created in whole or in part by me and I have 37 | the right to submit it under the open source license indicated in 38 | the file; or 39 | 40 | (b) The contribution is based upon previous work that, to the best of 41 | my knowledge, is covered under an appropriate open source License 42 | and I have the right under that license to submit that work with 43 | modifications, whether created in whole or in part by me, under 44 | the same open source license (unless I am permitted to submit 45 | under a different license), as indicated in the file; or 46 | 47 | (c) The contribution was provided directly to me by some other person 48 | who certified (a), (b) or (c) and I have not modified it. 49 | 50 | (d) The contribution is made free of any other party's intellectual 51 | property claims or rights. 52 | 53 | (e) I understand and agree that this project and the contribution are 54 | public and that a record of the contribution (including all 55 | personal information I submit with it, including my sign-off) is 56 | maintained indefinitely and may be redistributed consistent with 57 | this project or the open source license(s) involved. 58 | 59 | 60 | then you just add a line saying 61 | 62 | Signed-off-by: Random J Developer 63 | 64 | using your real name (sorry, no pseudonyms or anonymous contributions.) 65 | -------------------------------------------------------------------------------- /templates/_icons.scss: -------------------------------------------------------------------------------- 1 | /// 2 | /// THIS IS A GENERATED FILE!!! 3 | /// 4 | 5 | $icon-font-base-name: "{{ fontName }}"; 6 | $icon-font-path: "{{ fontPath }}"; 7 | $icon-font-class-prefix: "{{ classNamePrefix }}"; 8 | 9 | $font-icons: ({% _.each(glyphs, function(glyph) { %} 10 | {{ glyph.name }}: "\{{ glyph.content }}",{% }); %} 11 | ); 12 | 13 | @font-face { 14 | font-family: $icon-font-base-name; 15 | src: url("#{$icon-font-path}#{$icon-font-base-name}.eot?cachebust=#{random(99999)}"); 16 | src: /* stylelint-disable-line declaration-colon-space-after */ 17 | url("#{$icon-font-path}#{$icon-font-base-name}.eot?cachebust=#{random(99999)}#iefix") format("eot"), 18 | url("#{$icon-font-path}#{$icon-font-base-name}.woff?cachebust=#{random(99999)}") format("woff"), 19 | url("#{$icon-font-path}#{$icon-font-base-name}.ttf?cachebust=#{random(99999)}") format("truetype"), 20 | url("#{$icon-font-path}#{$icon-font-base-name}.svg?cachebust=#{random(99999)}#icons") format("svg"); 21 | font-weight: normal; 22 | font-style: normal; 23 | } 24 | 25 | @mixin font-icon-base() { 26 | font-family: $icon-font-base-name; 27 | display: inline-block; 28 | vertical-align: middle; 29 | line-height: 1; 30 | font-weight: normal; 31 | font-style: normal; 32 | speak: none; 33 | text-decoration: inherit; 34 | text-transform: none; 35 | text-rendering: optimizeLegibility; 36 | } 37 | 38 | @mixin font-icon-replace($pseudo) { 39 | position: relative; 40 | right: 9999px; 41 | &:#{$pseudo} { /* stylelint-disable-line */ 42 | position: absolute; 43 | height: 100%; 44 | text-align: center; 45 | top: 0; 46 | right: -9999px; 47 | } 48 | } 49 | 50 | /// Main Icon mixin. 51 | /// @param {String} $icon - Machine name of icon (filename). 52 | /// @param {String} $pseudo [before] - `before` | `after` The pseudo element to place the icon in. 53 | /// @todo Allow `$pseudo: false` to be declared so we don't have to use a pseudo element if we don't want to. 54 | /// @param {Bool} $text-replace [false] 55 | /// @param {String} $size [inherit] 56 | /// @param {String} $color [inherit] 57 | /// @param {Bool} $block [false] - If `display: block` should be applied. 58 | /// @example SCSS 59 | /// .class { 60 | /// @include icon('close'); 61 | /// } 62 | @mixin icon( 63 | $icon: "search", // just a default 64 | $pseudo: before, 65 | $text-replace: false, 66 | $size: inherit, 67 | $color: inherit, 68 | $block: false 69 | ) { 70 | &:#{$pseudo} { /* stylelint-disable-line */ 71 | 72 | @include font-icon-base(); 73 | content: map-get($font-icons, $icon); 74 | font-size: $size; 75 | color: $color; 76 | 77 | @content; 78 | } 79 | // Replace text with icon, like classic sprites 80 | @if $text-replace { 81 | @include font-icon-replace($pseudo); 82 | } 83 | // Get around fighting with line-heights 84 | @if $block { 85 | display: block; 86 | } 87 | } 88 | 89 | /** 90 | * Font application to generic DOM 91 | */ 92 | 93 | //@font-face { 94 | // font-family: $icon-font-base-name; 95 | // src: $icon-font-source-1; 96 | // src: $icon-font-source-2; 97 | // font-weight: normal; 98 | // font-style: normal; 99 | //} 100 | 101 | // Everything with .icon--something has a base set of styles in order to view 102 | [class*="#{$icon-font-class-prefix}--"] { 103 | @include font-icon-base; 104 | } 105 | 106 | // Print .icon--thingy classes using default :before for easy elements 107 | @each $icon-name, $icon-symbol in $font-icons { 108 | .#{$icon-font-class-prefix}--#{$icon-name}::before { 109 | content: map-get($font-icons, $icon-name); 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /lib/webpack.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const eslint = require('gulp-eslint'); 4 | const cached = require('gulp-cached'); 5 | const webpack = require('webpack'); 6 | const browserSync = require('browser-sync'); 7 | const core = require('./core'); 8 | const through2 = require('through2'); 9 | const clone = require('clone'); 10 | 11 | module.exports = (gulp, config, tasks) => { 12 | if (!config.webpack.config) { 13 | process.stdout.write(`Config passed in requires webpack config. gulpfile.yml file should contain this: 14 | webpack: 15 | enabled: true 16 | config: './webpack.config.js' 17 | Note: you can copy the template from templates/webpack.config.js. 18 | `); 19 | process.exit(1); 20 | } 21 | 22 | function validateJs() { 23 | return gulp.src(config.webpack.eslint.src) 24 | .pipe(cached('validate:webpack')) 25 | .pipe(eslint()) 26 | .pipe(eslint.format()); 27 | } 28 | 29 | validateJs.description = 'Lint webpack.'; 30 | 31 | if (config.webpack.eslint.enabled) { 32 | gulp.task('validate:webpack', () => validateJs().pipe(eslint.failAfterError())); 33 | tasks.validate.push('validate:webpack'); 34 | gulp.task('watch:validate:webpack', () => gulp.watch(config.webpack.eslint.src, validateJs)); 35 | tasks.watch.push('watch:validate:webpack'); 36 | } 37 | 38 | function compileWebpack() { 39 | return gulp.src(config.webpack.config) 40 | .pipe(function runCompileWebpack() { 41 | return through2.obj((file, enc, done) => { 42 | // Config options - https://webpack.js.org/configuration/ 43 | let webpackConfig; 44 | try { webpackConfig = clone(require(file.path)); } catch (error) { return done(error); } 45 | if (!webpackConfig.plugins) webpackConfig.plugins = []; 46 | if (typeof webpackConfig.devtool === 'undefined') webpackConfig.devtool = 'cheap-module-source-map'; 47 | 48 | return webpack(webpackConfig).run((err, stats) => { 49 | if (err) { 50 | console.error(err.stack || err); 51 | if (err.details) { 52 | console.error(err.details); 53 | } 54 | done(err); 55 | } 56 | 57 | // Stats config options: https://webpack.js.org/configuration/stats/ 58 | console.log(stats.toString({ 59 | chunks: false, // Makes the build much quieter 60 | colors: true // Shows colors in the console 61 | })); 62 | 63 | done(stats.hasErrors() ? core.error('webpack Compile Failed.') : null); 64 | }); 65 | }); 66 | }()); 67 | } 68 | 69 | gulp.task('webpack', compileWebpack); 70 | 71 | function watchWebpack() { 72 | return gulp.src(config.webpack.config) 73 | .pipe(function runWatchWebpack() { 74 | return through2.obj((file, enc, done) => { 75 | // Config options - https://webpack.js.org/configuration/ 76 | let webpackConfig; 77 | try { webpackConfig = clone(require(file.path)); } catch (error) { return done(error); } 78 | if (!webpackConfig.plugins) webpackConfig.plugins = []; 79 | if (typeof webpackConfig.devtool === 'undefined') webpackConfig.devtool = 'cheap-module-source-map'; 80 | 81 | webpackConfig.plugins.push(new webpack.LoaderOptionsPlugin({ 82 | debug: true 83 | })); 84 | 85 | return webpack(webpackConfig).watch({ 86 | // https://webpack.js.org/configuration/watch/#watchoptions 87 | }, (err, stats) => { 88 | if (err) { 89 | console.error(err.stack || err); 90 | if (err.details) { 91 | console.error(err.details); 92 | } 93 | done(err); 94 | } 95 | 96 | // Stats config options: https://webpack.js.org/configuration/stats/ 97 | console.log(stats.toString({ 98 | chunks: false, // Makes the build much quieter 99 | colors: true // Shows colors in the console 100 | })); 101 | 102 | if (config.browserSync.enabled) browserSync.get('server').reload(); 103 | }); 104 | }); 105 | }()); 106 | } 107 | 108 | gulp.task('watch:webpack', watchWebpack); 109 | 110 | tasks.watch.push('watch:webpack'); 111 | tasks.compile.push('webpack'); 112 | }; 113 | -------------------------------------------------------------------------------- /lib/js.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const eslint = require('gulp-eslint'); 4 | const sourcemaps = require('gulp-sourcemaps'); 5 | const uglify = require('gulp-uglify'); 6 | const concat = require('gulp-concat'); 7 | const babel = require('gulp-babel'); 8 | const cached = require('gulp-cached'); 9 | const gulpif = require('gulp-if'); 10 | const del = require('del'); 11 | const browserSync = require('browser-sync'); 12 | const bowerFiles = require('main-bower-files'); 13 | const path = require('path'); 14 | 15 | module.exports = (gulp, config, tasks) => { 16 | function validateJs() { 17 | return gulp.src(config.js.eslint.src) 18 | .pipe(cached('validate:js')) 19 | .pipe(eslint()) 20 | .pipe(eslint.format()); 21 | } 22 | 23 | validateJs.description = 'Lint JS.'; 24 | 25 | if (config.js.eslint.enabled) { 26 | gulp.task('validate:js', () => validateJs().pipe(eslint.failAfterError())); 27 | tasks.validate.push('validate:js'); 28 | gulp.task('watch:validate:js', () => gulp.watch(config.js.eslint.src, validateJs)); 29 | tasks.watch.push('watch:validate:js'); 30 | } 31 | 32 | function compileJs(done) { 33 | gulp.src(config.js.src) 34 | .pipe(gulpif(config.js.sourceMap.enabled, sourcemaps.init())) 35 | .pipe(gulpif(config.js.babel, babel())) // all babel options handled in `.babelrc` 36 | .pipe(gulpif(config.js.concat, concat(config.js.destName))) 37 | .pipe(gulpif(config.js.uglify, uglify())) 38 | .pipe(gulpif(config.js.sourceMap.enabled, sourcemaps.write((config.js.sourceMapEmbed) ? null : './'))) 39 | .pipe(gulp.dest(config.js.dest)) 40 | .on('end', () => { 41 | if (config.browserSync.enabled) { 42 | browserSync.get('server').reload(); 43 | } 44 | done(); 45 | }); 46 | } 47 | 48 | compileJs.description = 'Transpile JS using Babel, concat and uglify.'; 49 | 50 | gulp.task('js', compileJs); 51 | 52 | gulp.task('watch:js', () => gulp.watch(config.js.src, compileJs)); 53 | 54 | gulp.task('clean:js', (done) => { 55 | del([ 56 | `${config.js.dest}*.{js,js.map}` 57 | ], { force: true }).then(() => { 58 | done(); 59 | }); 60 | }); 61 | 62 | /** 63 | * Bundle up Bower JS Dependencies. 64 | * Creates `bower--{devDeps,deps}.min.js` in `config.js.dest`. 65 | * @param devDeps {boolean} If true, just devDeps, else just deps. 66 | * @param done {function} 67 | */ 68 | function bundleBower(done, devDeps) { 69 | const exclusions = config.js.bundleBowerExclusions; 70 | const files = bowerFiles({ 71 | paths: './', 72 | filter: (filePath) => { 73 | let isExcluded = false; 74 | if (exclusions && exclusions.length) { 75 | const directories = filePath.split('/'); 76 | // see if any directory name matches anything in the excluded list 77 | isExcluded = directories.some(dir => exclusions.some(item => item === dir)); 78 | } 79 | return path.extname(filePath) === '.js' && !(isExcluded); 80 | }, 81 | includeDev: devDeps ? 'exclusive' : false // `'exclusive'` does just devDeps w/o deps 82 | }); 83 | if (files.length) { 84 | gulp.src(files) 85 | .pipe(gulpif(config.js.sourceMap.enabled, sourcemaps.init())) 86 | .pipe(concat(`bower--${devDeps ? 'devDeps' : 'deps'}.min.js`)) 87 | .pipe(gulpif(config.js.uglify, uglify())) 88 | .pipe(gulpif(config.js.sourceMap.enabled, sourcemaps.write((config.js.sourceMapEmbed) ? null : './'))) 89 | .pipe(gulp.dest(config.js.dest)) 90 | .on('end', () => { 91 | process.stdout.write(`Bower ${devDeps ? 'devDeps' : 'deps'} bundled: ${files.map(file => path.basename(file)).join(', ')}.\n`); 92 | done(); 93 | }); 94 | } else { 95 | done(); 96 | } 97 | } 98 | 99 | if (config.js.bundleBower) { 100 | gulp.task('js:bundleBower', gulp.parallel( 101 | function bundleBowerDeps(done) { bundleBower(done); }, 102 | function bundleBowerDevDeps(done) { bundleBower(done, true); } 103 | )); 104 | 105 | const bowerBasePath = config.js.bowerBasePath || './'; 106 | gulp.task('watch:bower', () => { 107 | gulp.watch(path.join(bowerBasePath, 'bower.json'), gulp.series('js:bundleBower')); 108 | }); 109 | tasks.compile.push('js:bundleBower'); 110 | tasks.watch.push('watch:bower'); 111 | } 112 | 113 | tasks.clean.push('clean:js'); 114 | tasks.compile.push('js'); 115 | tasks.watch.push('watch:js'); 116 | }; 117 | -------------------------------------------------------------------------------- /docs/usage.md: -------------------------------------------------------------------------------- 1 | ## Config 2 | 3 | All is easily configurable by changing values in your `gulpfile.yml` file in your project. These values are merged into the `gulpfile.default.yml` file. 4 | 5 | For example, you can enable SCSS and JS like this: 6 | ```yml 7 | css: 8 | enabled: true 9 | js: 10 | enabled: true 11 | ``` 12 | 13 | You can find all the available options and defaults settings inside the `gulpfile.default.yml` file. 14 | 15 | 16 | ## Folders structure 17 | 18 | - source/ (only for Pattern Lab) 19 | - _annotations/ ([annotations](http://patternlab.io/docs/pattern-adding-annotations.html) for Patterns) 20 | - _data/ (Global JSON data files available to all Patterns, can add multiple) 21 | - _patterns/ (Twig, Scss, and JS all in here) 22 | - 00-base/ (Twig Namespace: `@base`) 23 | - Contains what all that follows needs: variables, mixins, and grid layouts for examples 24 | - 01-atoms/ (Twig Namespace: `@atoms`) 25 | - 02-molecules (Twig Namespace: `@molecules`) 26 | - 03-organisms (Twig Namespace: `@organisms`) 27 | - 04-templates (Twig Namespace: `@templates`) 28 | - 05-pages (Twig Namespace: `@pages`) 29 | - _meta/ (contains the header and footer Twig templates for PL; add any `` or `