commit ed1414567d5a34990873f5d9202b724dabe2ce09
parent c882f383dc0f1f9316e4a45499abe9fd33ab02e0
Author: Konstantin Tarkus <hello@tarkus.me>
Date: Thu, 6 Aug 2015 23:05:27 +0300
Replace Gulp with custom build scripts
Diffstat:
13 files changed, 375 insertions(+), 265 deletions(-)
diff --git a/.gitignore b/.gitignore
@@ -3,4 +3,5 @@
build
node_modules
+ncp-debug.log
npm-debug.log
diff --git a/README.md b/README.md
@@ -48,7 +48,7 @@ Join [#react-starter-kit](https://gitter.im/kriasoft/react-starter-kit) chatroom
│ ├── /utils/ # Utility classes and functions
│ ├── /app.js # Client-side startup script
│ └── /server.js # Server-side startup script
-│── gulpfile.babel.js # Configuration file for automated builds
+├── /tools/ # Build automation scripts and utilities
│── package.json # The list of 3rd party libraries and utilities
│── preprocessor.js # ES6 transpiler settings for Jest
└── webpack.config.js # Webpack configuration for bundling and optimization
@@ -63,37 +63,38 @@ Just [clone](github-windows://openRepo/https://github.com/kriasoft/react-starter
$ git clone -o react-starter-kit -b master --single-branch \
https://github.com/kriasoft/react-starter-kit.git MyApp
$ cd MyApp
-$ npm install -g gulp # Install Gulp task runner globally
$ npm install # Install Node.js components listed in ./package.json
+$ npm start # Compile and launch
```
### How to Build
```shell
-$ gulp build # or, `gulp build --release`
+$ npm run build # or, `npm run build --release`
```
-By default, it builds in debug mode. If you need to build in release mode, add
-`--release` flag. This will minimize your JavaScript; you will also see some warnings from
-[uglify](https://github.com/mishoo/UglifyJS) where it removes unused code from your release.
+By default, it builds in a *debug* mode. If you need to build in a release
+mode, just add `--release` flag. This will optimize the output bundle for
+production deployment; you will also see some warnings from
+[uglify](https://github.com/mishoo/UglifyJS) where it removes unused code from
+your release.
### How to Run
```shell
-$ gulp # or, `gulp --release`
+$ npm start # or, `npm start --release`
```
-This will start a lightweight development server with LiveReload and
+This will start a lightweight development server with "live reload" and
synchronized browsing across multiple devices and browsers.
### How to Deploy
```shell
-$ gulp build --release # Builds the project in release mode
-$ gulp deploy # or, `gulp deploy --production`
+$ npm run deploy # or, `npm run deploy --production`
```
-For more information see `deploy` task in `gulpfile.babel.js`.
+For more information see `tools/deploy.js`.
### How to Update
@@ -150,4 +151,10 @@ the file is. Name the test by appending `-test.js` to the js file.
### License
-The MIT License © Konstantin Tarkus ([@koistya](https://twitter.com/koistya)), [Kriasoft](http://www.kriasoft.com)
+Copyright © 2014-2015 Kriasoft, LLC. This source code is licensed under the MIT
+license found in the [LICENSE.txt](https://github.com/kriasoft/react-starter-kit/blob/master/LICENSE.txt)
+file. The documentation to the project is licensed under the
+[CC BY-SA 4.0](http://creativecommons.org/licenses/by-sa/4.0/) license.
+
+---
+Made with ♥ by Konstantin Tarkus ([@koistya](https://twitter.com/koistya)) and [contributors](https://github.com/kriasoft/react-starter-kit/graphs/contributors)
diff --git a/gulpfile.babel.js b/gulpfile.babel.js
@@ -1,186 +0,0 @@
-/*
- * React.js Starter Kit
- * Copyright (c) Konstantin Tarkus (@koistya), KriaSoft LLC
- *
- * This source code is licensed under the MIT license found in the
- * LICENSE.txt file in the root directory of this source tree.
- */
-
-import path from 'path';
-import cp from 'child_process';
-import gulp from 'gulp';
-import gulpLoadPlugins from 'gulp-load-plugins';
-import del from 'del';
-import mkdirp from 'mkdirp';
-import runSequence from 'run-sequence';
-import webpack from 'webpack';
-import minimist from 'minimist';
-
-const $ = gulpLoadPlugins();
-const argv = minimist(process.argv.slice(2));
-const src = Object.create(null);
-
-let watch = false;
-let browserSync;
-
-// The default task
-gulp.task('default', ['sync']);
-
-// Clean output directory
-gulp.task('clean', cb => {
- del(['.tmp', 'build/*', '!build/.git'], {dot: true}, () => {
- mkdirp('build/public', cb);
- });
-});
-
-// Static files
-gulp.task('assets', () => {
- src.assets = 'src/public/**';
- return gulp.src(src.assets)
- .pipe($.changed('build/public'))
- .pipe(gulp.dest('build/public'))
- .pipe($.size({title: 'assets'}));
-});
-
-// Resource files
-gulp.task('resources', () => {
- src.resources = [
- 'package.json',
- 'src/content*/**',
- 'src/templates*/**'
- ];
- return gulp.src(src.resources)
- .pipe($.changed('build'))
- .pipe(gulp.dest('build'))
- .pipe($.size({title: 'resources'}));
-});
-
-// Bundle
-gulp.task('bundle', cb => {
- const config = require('./webpack.config.js');
- const bundler = webpack(config);
- const verbose = !!argv.verbose;
- let bundlerRunCount = 0;
-
- function bundle(err, stats) {
- if (err) {
- throw new $.util.PluginError('webpack', err);
- }
-
- console.log(stats.toString({
- colors: $.util.colors.supportsColor,
- hash: verbose,
- version: verbose,
- timings: verbose,
- chunks: verbose,
- chunkModules: verbose,
- cached: verbose,
- cachedAssets: verbose
- }));
-
- if (++bundlerRunCount === (watch ? config.length : 1)) {
- return cb();
- }
- }
-
- if (watch) {
- bundler.watch(200, bundle);
- } else {
- bundler.run(bundle);
- }
-});
-
-// Build the app from source code
-gulp.task('build', ['clean'], cb => {
- runSequence(['assets', 'resources'], ['bundle'], cb);
-});
-
-// Build and start watching for modifications
-gulp.task('build:watch', cb => {
- watch = true;
- runSequence('build', () => {
- gulp.watch(src.assets, ['assets']);
- gulp.watch(src.resources, ['resources']);
- cb();
- });
-});
-
-// Launch a Node.js/Express server
-gulp.task('serve', ['build:watch'], cb => {
- src.server = [
- 'build/server.js',
- 'build/content/**/*',
- 'build/templates/**/*'
- ];
- let started = false;
- let server = (function startup() {
- const child = cp.fork('build/server.js', {
- env: Object.assign({NODE_ENV: 'development'}, process.env)
- });
- child.once('message', message => {
- if (message.match(/^online$/)) {
- if (browserSync) {
- browserSync.reload();
- }
- if (!started) {
- started = true;
- gulp.watch(src.server, function() {
- $.util.log('Restarting development server.');
- server.kill('SIGTERM');
- server = startup();
- });
- cb();
- }
- }
- });
- return child;
- })();
-
- process.on('exit', () => server.kill('SIGTERM'));
-});
-
-// Launch BrowserSync development server
-gulp.task('sync', ['serve'], cb => {
- browserSync = require('browser-sync');
-
- browserSync({
- logPrefix: 'RSK',
- notify: false,
- // Run as an https by setting 'https: true'
- // Note: this uses an unsigned certificate which on first access
- // will present a certificate warning in the browser.
- https: false,
- // Informs browser-sync to proxy our Express app which would run
- // at the following location
- proxy: 'localhost:5000'
- }, cb);
-
- process.on('exit', () => browserSync.exit());
-
- gulp.watch(['build/**/*.*'].concat(
- src.server.map(file => '!' + file)
- ), file => {
- browserSync.reload(path.relative(__dirname, file.path));
- });
-});
-
-// Deploy via Git
-gulp.task('deploy', cb => {
- const push = require('git-push');
- const remote = argv.production ?
- 'https://github.com/{user}/{repo}.git' :
- 'https://github.com/{user}/{repo}-test.git';
- push('./build', remote, cb);
-});
-
-// Run PageSpeed Insights
-gulp.task('pagespeed', cb => {
- const pagespeed = require('psi');
- // Update the below URL to the public URL of your site
- pagespeed('example.com', {
- strategy: 'mobile'
- // By default we use the PageSpeed Insights free (no API key) tier.
- // Use a Google Developer API key if you have one: http://goo.gl/RkN0vE
- // key: 'YOUR_API_KEY'
- }, cb);
-});
diff --git a/package.json b/package.json
@@ -10,7 +10,7 @@
"eventemitter3": "1.1.1",
"express": "4.13.3",
"fastclick": "1.0.6",
- "fbjs": "0.1.0-alpha.6",
+ "fbjs": "0.1.0-alpha.7",
"flux": "2.0.3",
"front-matter": "1.0.0",
"jade": "1.11.0",
@@ -31,28 +31,26 @@
"csscomb": "^3.1.8",
"cssnext": "^1.8.3",
"del": "^1.2.0",
- "eslint": "^1.0.0",
- "eslint-loader": "^0.14.2",
- "eslint-plugin-react": "^3.2.0",
+ "eslint": "^1.1.0",
+ "eslint-loader": "^1.0.0",
+ "eslint-plugin-react": "^3.2.2",
"git-push": "^0.1.1",
- "gulp": "^3.9.0",
- "gulp-changed": "^1.3.0",
- "gulp-if": "^1.2.5",
- "gulp-load-plugins": "^1.0.0-rc.1",
- "gulp-rename": "^1.2.2",
- "gulp-size": "^1.2.3",
- "gulp-util": "^3.0.6",
+ "glob": "^5.0.14",
"jest-cli": "^0.4.18",
- "minimist": "^1.1.2",
+ "minimist": "^1.1.3",
"mkdirp": "^0.5.1",
+ "ncp": "^2.0.0",
"postcss": "^4.1.16",
"postcss-loader": "^0.5.1",
"postcss-nested": "^0.3.2",
"psi": "^1.0.6",
+ "react-hot-loader": "^1.2.8",
"run-sequence": "^1.1.2",
"style-loader": "^0.12.3",
"url-loader": "^0.5.6",
- "webpack": "^1.11.0"
+ "webpack": "^1.11.0",
+ "webpack-dev-middleware": "^1.2.0",
+ "webpack-hot-middleware": "^1.2.0"
},
"jest": {
"rootDir": "./src",
@@ -62,12 +60,15 @@
]
},
"scripts": {
- "start": "node server.js",
- "lint": "eslint src gulpfile.babel.js webpack.config.js && csscomb src/components --lint",
- "comb": "csscomb src/components --verbose",
+ "lint": "eslint src tools",
+ "csslint": "csscomb src/components --lint --verbose",
+ "csscomb": "csscomb src/components --verbose",
"test": "eslint src && jest",
- "build": "gulp build",
- "serve": "gulp serve",
- "sync": "gulp sync"
+ "clean": "babel-node --eval \"require('./tools/clean')().catch(err => console.log(err.stack))\"",
+ "copy": "babel-node --eval \"require('./tools/copy')().catch(err => console.log(err.stack))\"",
+ "bundle": "babel-node --eval \"require('./tools/bundle')().catch(err => console.log(err.stack))\"",
+ "build": "babel-node --eval \"require('./tools/build')().catch(err => console.log(err.stack))\"",
+ "serve": "babel-node --eval \"require('./tools/serve')().catch(err => console.log(err.stack))\"",
+ "start": "babel-node --eval \"require('./tools/start')().catch(err => console.log(err.stack))\""
}
}
diff --git a/preprocessor.js b/preprocessor.js
@@ -1,6 +1,7 @@
-/*
- * React.js Starter Kit
- * Copyright (c) Konstantin Tarkus (@koistya), KriaSoft LLC
+/**
+ * React Starter Kit (http://www.reactstarterkit.com/)
+ *
+ * Copyright © 2014-2015 Kriasoft, LLC. All rights reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE.txt file in the root directory of this source tree.
diff --git a/tools/build.js b/tools/build.js
@@ -0,0 +1,20 @@
+/**
+ * React Starter Kit (http://www.reactstarterkit.com/)
+ *
+ * Copyright © 2014-2015 Kriasoft, LLC. All rights reserved.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE.txt file in the root directory of this source tree.
+ */
+
+/**
+ * Compiles the project from source files into a distributable
+ * format and copies it to the output (build) folder.
+ */
+export default async () => {
+ console.log('build');
+ await require('./clean')();
+ await require('./copy')();
+ await require('./bundle')();
+};
+
diff --git a/tools/bundle.js b/tools/bundle.js
@@ -0,0 +1,52 @@
+/**
+ * React Starter Kit (http://www.reactstarterkit.com/)
+ *
+ * Copyright © 2014-2015 Kriasoft, LLC. All rights reserved.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE.txt file in the root directory of this source tree.
+ */
+
+import webpack from 'webpack';
+import config from '../webpack.config';
+import minimist from 'minimist';
+
+const argv = minimist(process.argv.slice(2));
+
+/**
+ * Bundles JavaScript, CSS and images into one or more packages
+ * ready to be used in a browser.
+ */
+export default async () => new Promise((resolve, reject) => {
+ console.log('bundle');
+ const bundler = webpack(config);
+ const verbose = !!argv.verbose;
+ let bundlerRunCount = 0;
+
+ function bundle(err, stats) {
+ if (err) {
+ return reject(err);
+ }
+
+ console.log(stats.toString({
+ colors: true,
+ hash: verbose,
+ version: verbose,
+ timings: verbose,
+ chunks: verbose,
+ chunkModules: verbose,
+ cached: verbose,
+ cachedAssets: verbose
+ }));
+
+ if (++bundlerRunCount === (global.watch ? config.length : 1)) {
+ return resolve();
+ }
+ }
+
+ if (global.watch) {
+ bundler.watch(200, bundle);
+ } else {
+ bundler.run(bundle);
+ }
+});
diff --git a/tools/clean.js b/tools/clean.js
@@ -0,0 +1,25 @@
+/**
+ * React Starter Kit (http://www.reactstarterkit.com/)
+ *
+ * Copyright © 2014-2015 Kriasoft, LLC. All rights reserved.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE.txt file in the root directory of this source tree.
+ */
+
+import del from 'del';
+import fs from './lib/fs';
+
+/**
+ * Cleans up the output (build) directory.
+ */
+export default () => new Promise((resolve, reject) => {
+ console.log('clean');
+ del(['.tmp', 'build/*', '!build/.git'], {dot: true}, err => {
+ if (err) {
+ reject(err);
+ } else {
+ fs.makeDir('build/public').then(resolve, reject);
+ }
+ });
+});
diff --git a/tools/copy.js b/tools/copy.js
@@ -0,0 +1,53 @@
+/**
+ * React Starter Kit (http://www.reactstarterkit.com/)
+ *
+ * Copyright © 2014-2015 Kriasoft, LLC. All rights reserved.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE.txt file in the root directory of this source tree.
+ */
+
+import { ncp as copy } from 'ncp';
+
+/**
+ * Copies static files such as robots.txt, favicon.ico to the
+ * output (build) folder.
+ */
+export default () => {
+ console.log('copy');
+ return Promise.all([
+
+ // Static files
+ new Promise((resolve, reject) => {
+ copy('src/public', 'build/public', err => {
+ if (err) {
+ reject(err);
+ } else {
+ resolve();
+ }
+ })
+ }),
+
+ // Files with content (e.g. *.md files)
+ new Promise((resolve, reject) => {
+ copy('src/content', 'build/content', err => {
+ if (err) {
+ reject(err);
+ } else {
+ resolve();
+ }
+ })
+ }),
+
+ // Website and email templates
+ new Promise((resolve, reject) => {
+ copy('src/templates', 'build/templates', err => {
+ if (err) {
+ reject(err);
+ } else {
+ resolve();
+ }
+ })
+ })
+ ]);
+};
diff --git a/tools/lib/fs.js b/tools/lib/fs.js
@@ -0,0 +1,33 @@
+/**
+ * React Starter Kit (http://www.reactstarterkit.com/)
+ *
+ * Copyright © 2014-2015 Kriasoft, LLC. All rights reserved.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE.txt file in the root directory of this source tree.
+ */
+
+import fs from 'fs';
+import mkdirp from 'mkdirp';
+
+const writeFile = (filename, contents) => new Promise((resolve, reject) => {
+ fs.writeFile(filename, contents, 'utf8', err => {
+ if (err) {
+ reject(err);
+ } else {
+ resolve();
+ }
+ });
+});
+
+const makeDir = name => new Promise((resolve, reject) => {
+ mkdirp(name, err => {
+ if (err) {
+ reject(err);
+ } else {
+ resolve();
+ }
+ });
+});
+
+export default { writeFile, makeDir };
diff --git a/tools/serve.js b/tools/serve.js
@@ -0,0 +1,28 @@
+/**
+ * React Starter Kit (http://www.reactstarterkit.com/)
+ *
+ * Copyright © 2014-2015 Kriasoft, LLC. All rights reserved.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE.txt file in the root directory of this source tree.
+ */
+
+import path from 'path';
+import cp from 'child_process';
+
+/**
+ * Launches Node.js/Express web server in a separate (forked) process.
+ */
+export default () => new Promise((resolve, reject) => {
+ console.log('serve');
+ const server = cp.fork(path.join(__dirname, '../build/server.js'), {
+ env: Object.assign({NODE_ENV: 'development'}, process.env)
+ });
+ server.once('message', message => {
+ if (message.match(/^online$/)) {
+ resolve();
+ }
+ });
+ server.once('error', err => reject(error));
+ process.on('exit', () => server.kill('SIGTERM'));
+});
diff --git a/tools/start.js b/tools/start.js
@@ -0,0 +1,61 @@
+/**
+ * React Starter Kit (http://www.reactstarterkit.com/)
+ *
+ * Copyright © 2014-2015 Kriasoft, LLC. All rights reserved.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE.txt file in the root directory of this source tree.
+ */
+
+import browserSync from 'browser-sync';
+import webpack from 'webpack';
+import webpackDevMiddleware from 'webpack-dev-middleware';
+import webpackHotMiddleware from 'webpack-hot-middleware';
+
+global.WATCH = true;
+const config = require('../webpack.config')[0]; // Client-side bundle config
+const bundler = webpack(config);
+
+/**
+ * Launches a development web server with "live reload" functionality -
+ * synchronizing URLs, interactions and code changes across multiple devices.
+ */
+export default async () => {
+
+ await require('./build')();
+ await require('./serve')();
+
+ browserSync({
+ proxy: {
+
+ target: 'localhost:5000',
+
+ middleware: [
+ webpackDevMiddleware(bundler, {
+ // IMPORTANT: dev middleware can't access config, so we should
+ // provide publicPath by ourselves
+ publicPath: config.output.publicPath,
+
+ // pretty colored output
+ stats: config.stats,
+
+ hot: true,
+ historyApiFallback: true
+
+ // for other settings see
+ // http://webpack.github.io/docs/webpack-dev-middleware.html
+ }),
+
+ // bundler should be the same as above
+ webpackHotMiddleware(bundler)
+ ]
+ },
+
+ // no need to watch '*.js' here, webpack will take care of it for us,
+ // including full page reloads if HMR won't work
+ files: [
+ 'build/public/**/*.css',
+ 'build/public/**/*.html'
+ ]
+ });
+};
diff --git a/webpack.config.js b/webpack.config.js
@@ -1,6 +1,7 @@
-/*
- * React.js Starter Kit
- * Copyright (c) Konstantin Tarkus (@koistya), KriaSoft LLC
+/**
+ * React Starter Kit (http://www.reactstarterkit.com/)
+ *
+ * Copyright © 2014-2015 Kriasoft, LLC. All rights reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE.txt file in the root directory of this source tree.
@@ -13,6 +14,8 @@ import minimist from 'minimist';
const argv = minimist(process.argv.slice(2));
const DEBUG = !argv.release;
+const WATCH = global.WATCH === undefined ? false : global.WATCH;
+const VERBOSE = global.VERBOSE === undefined ? false : global.VERBOSE;
const STYLE_LOADER = 'style-loader/useable';
const CSS_LOADER = DEBUG ? 'css-loader' : 'css-loader?minimize';
const AUTOPREFIXER_BROWSERS = [
@@ -37,7 +40,7 @@ const GLOBALS = {
const config = {
output: {
- publicPath: './',
+ publicPath: '/',
sourcePrefix: ' '
},
@@ -46,7 +49,14 @@ const config = {
stats: {
colors: true,
- reasons: DEBUG
+ reasons: DEBUG,
+ hash: VERBOSE,
+ version: VERBOSE,
+ timings: VERBOSE,
+ chunks: VERBOSE,
+ chunkModules: VERBOSE,
+ cached: VERBOSE,
+ cachedAssets: VERBOSE
},
plugins: [
@@ -58,36 +68,29 @@ const config = {
},
module: {
- loaders: [
- {
- test: /\.css$/,
- loader: `${STYLE_LOADER}!${CSS_LOADER}!postcss-loader`
- },
- {
- test: /\.gif/,
- loader: 'url-loader?limit=10000&mimetype=image/gif'
- },
- {
- test: /\.jpg/,
- loader: 'url-loader?limit=10000&mimetype=image/jpg'
- },
- {
- test: /\.png/,
- loader: 'url-loader?limit=10000&mimetype=image/png'
- },
- {
- test: /\.svg/,
- loader: 'url-loader?limit=10000&mimetype=image/svg+xml'
- },
- {
- test: /\.jsx?$/,
- include: [
- path.resolve(__dirname, 'node_modules/react-routing/src'),
- path.resolve(__dirname, 'src')
- ],
- loader: 'babel-loader'
- }
- ]
+ loaders: [{
+ test: /\.txt/,
+ loader: 'file?name=[path][name].[ext]'
+ }, {
+ test: /\.gif/,
+ loader: 'url-loader?limit=10000&mimetype=image/gif'
+ }, {
+ test: /\.jpg/,
+ loader: 'url-loader?limit=10000&mimetype=image/jpg'
+ }, {
+ test: /\.png/,
+ loader: 'url-loader?limit=10000&mimetype=image/png'
+ }, {
+ test: /\.svg/,
+ loader: 'url-loader?limit=10000&mimetype=image/svg+xml'
+ }, {
+ test: /\.jsx?$/,
+ include: [
+ path.resolve(__dirname, 'node_modules/react-routing/src'),
+ path.resolve(__dirname, 'src')
+ ],
+ loaders: [...(WATCH ? ['react-hot'] : []), 'babel-loader']
+ }]
},
postcss: [
@@ -102,9 +105,13 @@ const config = {
// -----------------------------------------------------------------------------
const appConfig = merge({}, config, {
- entry: './src/app.js',
+ entry: [...(WATCH ? [
+ 'webpack/hot/dev-server',
+ 'webpack-hot-middleware/client'] : []),
+ './src/app.js'
+ ],
output: {
- path: './build/public',
+ path: path.join(__dirname, 'build', 'public'),
filename: 'app.js'
},
devtool: DEBUG ? 'source-map' : false,
@@ -114,8 +121,17 @@ const appConfig = merge({}, config, {
new webpack.optimize.DedupePlugin(),
new webpack.optimize.UglifyJsPlugin(),
new webpack.optimize.AggressiveMergingPlugin()
- ])
- )
+ ]).concat(WATCH ? [
+ new webpack.HotModuleReplacementPlugin(),
+ new webpack.NoErrorsPlugin()
+ ] : [])
+ ),
+ module: {
+ loaders: [...config.module.loaders, {
+ test: /\.css$/,
+ loader: `${STYLE_LOADER}!${CSS_LOADER}!postcss-loader`
+ }]
+ }
});
//
@@ -154,12 +170,10 @@ const serverConfig = merge({}, config, {
{ raw: true, entryOnly: false })
),
module: {
- loaders: config.module.loaders.map(function(loader) {
- // Remove style-loader
- return merge(loader, {
- loader: loader.loader = loader.loader.replace(STYLE_LOADER + '!', '')
- });
- })
+ loaders: [...config.module.loaders, {
+ test: /\.css$/,
+ loader: `${CSS_LOADER}!postcss-loader`
+ }]
}
});