Skip to content

Commit

Permalink
Merge 5aa457b into 4454b7f
Browse files Browse the repository at this point in the history
  • Loading branch information
makao committed Apr 3, 2019
2 parents 4454b7f + 5aa457b commit 443353f
Show file tree
Hide file tree
Showing 15 changed files with 268 additions and 121 deletions.
71 changes: 61 additions & 10 deletions Readme.md
Expand Up @@ -11,26 +11,77 @@ Module for Sass processing during static content deployment with additional Gulp
2. Register module `php bin/magento setup:upgrade`
3. Compile Sass theme using `php bin/magento setup:static-content:deploy -f`

## Compatibility
## Example theme
* [clawrock/magento2-theme-blank-sass](https://github.com/clawrock/magento2-theme-blank-sass)

## Works with
#### Preprocessor
* Magento 2.2 - 2.3
* PHP 7.0 - 7.2
* Gulp 3.9
* Node.js v8 or later
#### Gulp
* Node.js 10+

## Gulp workflow
## Gulp
1. Install Node.js
2. Install Gulp configuration `php bin/magento dev:gulp:install`
3. Install Gulp and required dependencies `npm install`
4. Define theme configuration `php bin/magento dev:gulp:theme`
5. Symlink theme to pub/static folder `gulp exec:[theme_key]`
6. Compile Scss `gulp scss:[theme_key]`
6. Compile SCSS `gulp scss:[theme_key]`
7. Watch for changes `gulp watch:[theme_key]`

## Browsersync
Pass `--proxy http://magento.test` argument to `gulp watch` or `gulp watch:[theme_key]` where http://magento.test is Magento base url and Browsersync will be automatically enabled.
It also supports LESS, instead of SCSS use less like `gulp less:[theme_key]`

## Compatible themes
* [clawrock/magento2-theme-blank-sass](https://github.com/clawrock/magento2-theme-blank-sass)
Use additional flags to enable more watchers:
- `--phtml`: reload when phtml file is changed
- `--js`: reload when js file is changed

#### Configure theme
You can manually configure theme like in Gruntfile which is shipped with Magento or use `php bin/magento dev:gulp:theme` command which will configure it for you.

Reference: [Grunt configuration file](https://devdocs.magento.com/guides/v2.3/frontend-dev-guide/tools/using_grunt.html#grunt_config)

#### Commands
| Shortcut | Full command |
| ------------------------- | -------------------------------------------------------------- |
| `gulp build:scss:[theme]` | `gulp exec:[theme] && gulp scss:[theme]` |
| `gulp dev:scss:[theme]` | `gulp exec:[theme] && gulp scss:[theme] && gulp watch:[theme]` |

List of gulp commands:
- `gulp clean:[theme_key]`
- `gulp deploy:[theme_key]`
- `gulp exec:[theme_key]`
- `gulp scss:[theme_key]`
- `gulp less:[theme_key]`
- `gulp watch:[theme_key]`
- `gulp build:scss:[theme_key]`
- `gulp build:less:[theme_key]`
- `gulp dev:scss:[theme_key]`
- `gulp dev:less:[theme_key]`

#### BrowserSync
Pass `--proxy http://magento.test` argument to `gulp watch:[theme_key]` or `gulp dev:scss[theme_key]` where http://magento.test is Magento base url and BrowserSync will be enabled.

There are some BrowserSync options available (only string and boolean values are supported):
- `--bs-ui`: [ui](https://www.browsersync.io/docs/options#option-ui)
- `--bs-port`: [port](https://www.browsersync.io/docs/options#option-port)
- `--bs-logLevel`: [logLevel](https://www.browsersync.io/docs/options#option-logLevel)
- `--bs-logPrefix`: [logPrefix](https://www.browsersync.io/docs/options#option-logPrefix)
- `--bs-logConnections`: [logConnections](https://www.browsersync.io/docs/options#option-logConnections)
- `--bs-logFileChanges`: [logFileChanges](https://www.browsersync.io/docs/options#option-logFileChanges)
- `--bs-open`: [open](https://www.browsersync.io/docs/options#option-open)
- `--bs-browser`: [browser](https://www.browsersync.io/docs/options#option-browser)
- `--bs-notify`: [notify](https://www.browsersync.io/docs/options#option-notify)
- `--bs-scrollProportionally`: [scrollProportionally](https://www.browsersync.io/docs/options#option-scrollProportionally)
- `--bs-scrollThrottle`: [scrollThrottle](https://www.browsersync.io/docs/options#option-scrollThrottle)
- `--bs-reloadDelay`: [reloadDelay](https://www.browsersync.io/docs/options#option-reloadDelay)
- `--bs-reloadDebounce`: [reloadDebounce](https://www.browsersync.io/docs/options#option-reloadDebounce)
- `--bs-reloadThrottle`: [reloadThrottle](https://www.browsersync.io/docs/options#option-reloadThrottle)
- `--bs-injectChanges`: [injectChanges](https://www.browsersync.io/docs/options#option-injectChanges)
- `--bs-startPath`: [startPath](https://www.browsersync.io/docs/options#option-startPath)

#### Example usage
`gulp dev:scss:my_theme --proxy http://m2.test --phtml --bs-port=8080 --bs-open=0`

## Troubleshooting
* If you have previously installed Grunt, please make sure you have removed package-lock.json and node_modules folder. Then run `npm install`.
If you had previously installed Grunt, please make sure you have removed package-lock.json and node_modules folder. Then run `npm install`.
2 changes: 1 addition & 1 deletion dev/tools/.babelrc
@@ -1,3 +1,3 @@
{
"presets": ["env"]
"presets": ["@babel/preset-env"]
}
5 changes: 0 additions & 5 deletions dev/tools/gulp/config.js

This file was deleted.

16 changes: 4 additions & 12 deletions dev/tools/gulp/tasks/clean.js
@@ -1,19 +1,11 @@
import rimraf from 'gulp-rimraf';
import gulp from 'gulp';
import config from '../config';
import del from 'del';
import ThemeRegistry from '../utils/theme-registry';

export default function (done, theme) {
const themeRegistry = new ThemeRegistry();
const themeConfig = themeRegistry.getTheme(theme);

if (themeConfig) {
return gulp.src(`${config.projectPath}pub/static/${themeConfig.area}/${themeConfig.name}`, {read: false})
.pipe(rimraf({force: true}));
}

return gulp.src([
config.projectPath + 'pub/static/*',
'!' + config.projectPath + 'pub/static/.htaccess'
], {read: false}).pipe(rimraf({force: true}));
return del([
`${themeConfig.path}/**/*`
]);
}
8 changes: 1 addition & 7 deletions dev/tools/gulp/tasks/deploy.js
Expand Up @@ -5,13 +5,7 @@ export default function (done, theme) {
const themeRegistry = new ThemeRegistry();
const themeConfig = themeRegistry.getTheme(theme);

let command = `php bin/magento setup:static-content:deploy -f`;

if (themeConfig) {
command += ` --theme="${themeConfig.name}"`;
}

exec(command, (err, stdout, stderr) => {
exec(`php bin/magento setup:static-content:deploy -f --theme="${themeConfig.name}"`, (err, stdout, stderr) => {
console.log(stdout);
console.log(stderr);
done(err);
Expand Down
4 changes: 0 additions & 4 deletions dev/tools/gulp/tasks/exec.js
Expand Up @@ -5,10 +5,6 @@ export default function (done, theme) {
const themeRegistry = new ThemeRegistry();
const themeConfig = themeRegistry.getTheme(theme);

if (!themeConfig) {
throw new Error('Please specify theme after colon.');
}

exec(`php bin/magento dev:source-theme:deploy --type="${themeConfig.dsl}" --locale="${themeConfig.locale}" --area="${themeConfig.area}" --theme="${themeConfig.name}" ${themeConfig.files.join(' ')}`, (err, stdout, stderr) => {
console.log(stdout);
console.log(stderr);
Expand Down
11 changes: 4 additions & 7 deletions dev/tools/gulp/tasks/less.js
Expand Up @@ -2,19 +2,16 @@ import gulp from 'gulp';
import ThemeRegistry from '../utils/theme-registry';
import less from 'gulp-less';
import sourceMaps from 'gulp-sourcemaps';
import util from 'gulp-util';
import { sync } from '../utils/sync';
import log from 'fancy-log';
import { syncStream } from '../utils/sync';

export default function (done, theme) {
const themeRegistry = new ThemeRegistry();
const themeConfig = themeRegistry.getTheme(theme);
if (!themeConfig) {
throw new Error('Please specify theme after colon.');
}

return sync(gulp.src(themeConfig.preprocessorFiles)
return syncStream(gulp.src(themeConfig.preprocessorFiles)
.pipe(sourceMaps.init())
.pipe(less().on('error', util.log))
.pipe(less().on('error', log.error))
.pipe(sourceMaps.write('.'))
.pipe(gulp.dest(themeConfig.path + 'css/')));
}
7 changes: 2 additions & 5 deletions dev/tools/gulp/tasks/scss.js
Expand Up @@ -2,16 +2,13 @@ import gulp from 'gulp';
import sass from 'gulp-sass';
import sourceMaps from 'gulp-sourcemaps';
import ThemeRegistry from '../utils/theme-registry';
import { sync } from '../utils/sync';
import { syncStream } from '../utils/sync';

export default function (done, theme) {
const themeRegistry = new ThemeRegistry();
const themeConfig = themeRegistry.getTheme(theme);
if (!themeConfig) {
throw new Error('Please specify theme after colon.');
}

return sync(gulp.src(themeConfig.preprocessorFiles)
return syncStream(gulp.src(themeConfig.preprocessorFiles)
.pipe(sourceMaps.init())
.pipe(sass().on('error', sass.logError))
.pipe(sourceMaps.write('.'))
Expand Down
104 changes: 69 additions & 35 deletions dev/tools/gulp/tasks/watch.js
@@ -1,55 +1,89 @@
import gulp from 'gulp';
import AssetDeployer from '../utils/asset-deployer';
import ThemeRegistry from '../utils/theme-registry';
import config from '../config';
import { initSync } from '../utils/sync';
import { initSync, syncReload, isSyncEnabled } from '../utils/sync';
import path from 'path';
import gutil from 'gulp-util';
import log from 'fancy-log';
import chalk from 'chalk';

function runThemeTask(task, file) {
const themeRegistry = new ThemeRegistry();
const theme = themeRegistry.getThemeKeyByFile(file);
if (!theme) {
gutil.log(chalk.red(`Theme task not found for this file!`));
}

if (gulp.tasks[`${task}:${theme}`]) {
gulp.start(`${task}:${theme}`);

return;
}
gutil.log(chalk.red(`Task ${task}:${theme} not found!`));
}
import del from 'del';
import { argv } from 'yargs';

function relativizePath(absolutePath) {
return path.relative(process.cwd(), absolutePath);
}

export default function (done, theme) {
const pubPath = `${config.projectPath}/pub/static`;

initSync();
const assetDeployer = new AssetDeployer(theme);
const themeRegistry = new ThemeRegistry();
const themeConfig = themeRegistry.getTheme(theme);
const mainWatcher = gulp.watch(`${themeConfig.path}**/*.${themeConfig.dsl}`, gulp.series([`${themeConfig.dsl}:${theme}`]))
.on('change', path => {
log.info(chalk.white(`File ${relativizePath(path)} was changed`));
});

gulp.watch(`${themeConfig.sourcePath}**/*.${themeConfig.dsl}`)
.on('add', path => {
if (assetDeployer.isMagentoImportFile(path)) {
mainWatcher.unwatch(`${themeConfig.path}**/*.${themeConfig.dsl}`);
log.info(chalk.white(`File ${relativizePath(path)} detected as @magento_import, deploying source theme...`));
gulp.task(`exec:${theme}`)(() => {
mainWatcher.add(`${themeConfig.path}**/*.${themeConfig.dsl}`);
gulp.task(`${themeConfig.dsl}:${theme}`)();
});
return;
}

if (theme) {
const themeRegistry = new ThemeRegistry();
const themeConfig = themeRegistry.getTheme(theme);
gulp.watch(`${themeConfig.path}**/*.${themeConfig.dsl}`, [`${themeConfig.dsl}:${theme}`]).on('change', stream => {
gutil.log(chalk.white(`File ${relativizePath(stream.path)} was changed`));
gulp.src(path).pipe(gulp.symlink(assetDeployer.resolveSymlinkPath(path)));
log.info(chalk.white(`File ${relativizePath(path)} was created and linked pub`));
}).on('unlink', path => {
mainWatcher.unwatch(`${themeConfig.path}**/*.${themeConfig.dsl}`);
del([assetDeployer.resolveSymlinkPath(path)]).then(() => {
log.info(chalk.white(`File ${relativizePath(path)} was deleted`));
if (assetDeployer.isMagentoImportFile(path)) {
log.info(chalk.white(`File ${relativizePath(path)} detected as @magento_import, deploying source theme...`));
gulp.task(`exec:${theme}`)(() => {
mainWatcher.add(`${themeConfig.path}**/*.${themeConfig.dsl}`);
gulp.task(`${themeConfig.dsl}:${theme}`)();
});
return;
}
mainWatcher.add(`${themeConfig.path}**/*.${themeConfig.dsl}`);
});
});

return;
}
const requireJsCallback = cb => {
del([`${themeConfig.path}requirejs-config.js`]).then(() => {
log.info(chalk.white(`Combined RequireJS configuration file removed from pub/static.`));
cb();
});
};

gulp.watch(`${pubPath}/**/*.less`).on('change', stream => {
gutil.log(chalk.white(`File ${relativizePath(stream.path)} was changed`));
gulp.watch([
'app/code/**/requirejs-config.js',
`${themeConfig.sourcePath}**/requirejs-config.js`
], requireJsCallback);

runThemeTask(`less`, stream.path);
if (!isSyncEnabled()) {
return;
}

});
const reload = cb => {
syncReload();
cb();
};

gulp.watch(`${pubPath}/**/*.scss`).on('change', stream => {
gutil.log(chalk.white(`File ${relativizePath(stream.path)} was changed`));
if (argv.phtml) {
gulp.watch([
'app/code/**/*.phtml',
`${themeConfig.sourcePath}**/*.phtml`
], reload);
}

runThemeTask(`scss`, stream.path);
});
if (argv.js) {
gulp.watch([
'app/code/**/*.js',
`${themeConfig.sourcePath}**/*.js`
], reload);
}
}
48 changes: 48 additions & 0 deletions dev/tools/gulp/utils/asset-deployer.js
@@ -0,0 +1,48 @@
import ThemeRegistry from './theme-registry';
import fs from 'fs';

class AssetDeployer {
constructor(theme) {
this.theme = (new ThemeRegistry()).getTheme(theme);
this.magentoImport = {};
this.resolveMagentoImport();
}

resolveSymlinkPath(sourceFile) {
const destinationParts = sourceFile.split('/').splice(5).filter(part => part !== 'web');
destinationParts.pop();

return `${this.theme.path}${destinationParts.join('/')}`;
}

isMagentoImportFile(path) {
return Object.keys(this.magentoImport).some(file => {
return this.magentoImport[file].some(pattern => {
return path.includes(pattern);
});
});
}

resolveMagentoImport() {
this.theme.sourceFiles.forEach((file) => {
const data = fs.readFileSync(file, 'UTF-8');
const importRe = new RegExp('\/\/@magento_import[^;]*', 'gm');
const result = data.match(importRe);
if (!result) {
return;
}
result.forEach((line) => {
const lineRe = new RegExp('[\'"](.*)[\'"]');
const lineResult = line.match(lineRe);
if (lineResult) {
if (this.magentoImport[file] === undefined) {
this.magentoImport[file] = [];
}
this.magentoImport[file].push(lineResult[1]);
}
});
});
}
}

export default AssetDeployer;

0 comments on commit 443353f

Please sign in to comment.