├── .gitignore ├── src ├── styles │ ├── SidePanel.scss │ ├── main.scss │ └── _appstrap.scss ├── app │ ├── nls │ │ └── strings.js │ ├── templates │ │ └── SidePanel.html │ ├── utils.js │ ├── config.js │ ├── SidePanel.js │ ├── mapService.js │ └── App.js └── index.html ├── .babelrc ├── package.json ├── CHANGELOG.md ├── gulpfile.js └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .publish 2 | dist 3 | node_modules 4 | -------------------------------------------------------------------------------- /src/styles/SidePanel.scss: -------------------------------------------------------------------------------- 1 | .side-panel { 2 | position: absolute; 3 | top: 15px; 4 | right: 15px; 5 | width: 300px; 6 | z-index: 1; 7 | } 8 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "es2015", 5 | { 6 | "modules": false 7 | } 8 | ] 9 | ], 10 | "plugins": ["external-helpers"] 11 | } 12 | -------------------------------------------------------------------------------- /src/app/nls/strings.js: -------------------------------------------------------------------------------- 1 | /* global define */ 2 | define({ 3 | root: { 4 | About: 'About', 5 | sidePanelMessage: 'This application is implemented as Dojo widgets written in ES2015 bundled and minified using Rollup.js.' 6 | } 7 | }); 8 | -------------------------------------------------------------------------------- /src/app/templates/SidePanel.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

4 | 5 | 6 |

7 |
8 |
9 |
10 |

${nls.About}

11 |

${nls.sidePanelMessage}

12 |
13 |
14 |
15 | -------------------------------------------------------------------------------- /src/app/utils.js: -------------------------------------------------------------------------------- 1 | // with ES2015 dependencies you have the option of 2 | // importing their functions directly and 3 | // Rollup will include the function and others it references 4 | // in the bundled output along with the application code 5 | import capitalize from '../../node_modules/lodash-es/capitalize'; 6 | 7 | // place stateless utility functions in this file 8 | // they can be imported into other files as needed 9 | export function formatTitle (title) { 10 | if (!title.replace) { 11 | // not a string, just return 12 | return title; 13 | } 14 | 15 | // replace dashes and underscores w/ spaces 16 | return title.replace(/-|_/g, ' ').split(' ').map(word => { 17 | // and capitalize each word 18 | return capitalize(word); 19 | }).join(' '); 20 | } 21 | -------------------------------------------------------------------------------- /src/styles/main.scss: -------------------------------------------------------------------------------- 1 | /* load calcite variables as overrides */ 2 | @import 'calcite-bootstrap'; 3 | /* set the output folder for fonts */ 4 | $icon-font-path: './fonts/'; 5 | 6 | // contains only the bootstrap modules used in this app 7 | @import 'appstrap'; 8 | 9 | /* app layout */ 10 | html, body, #app, .map-container { 11 | width: 100%; 12 | height: 100%; 13 | margin: 0; 14 | display: block; 15 | } 16 | 17 | .esri-basemap-toggle { 18 | position: absolute; 19 | bottom: 45px; 20 | left: 15px; 21 | z-index: 30; 22 | } 23 | 24 | /* override vendor styles */ 25 | /* fix for bug when showing dark gray basemap title */ 26 | .esri-basemap-toggle { 27 | .esri-basemap-title { 28 | width: 100% 29 | } 30 | } 31 | 32 | /* components */ 33 | @import './SidePanel'; 34 | -------------------------------------------------------------------------------- /src/app/config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | mapProps: { 3 | center: [-122.33, 37.75], 4 | zoom: 11, 5 | basemap: 'dark-gray' 6 | }, 7 | secondaryBasemap: 'streets', 8 | fuelingStationLayerProps: { 9 | url: 'https://services.arcgis.com/rOo16HdIMeOBI4Mb/arcgis/rest/services/Alternative_Fueling_Stations/FeatureServer/0', 10 | popupTemplate: { 11 | title: '{Station_Na}', 12 | content: ` 13 |

{Street_Add}
14 | {City}, {State}, {ZIP}

15 | 16 |

Fuel Type: {Fuel_Type}

17 |

Phone: {Station_Ph}

18 |

Open to: {Groups_Wit}

19 |

Hours: {Access_Day}

` 20 | }, 21 | outFields: ['Station_Na', 'Street_Add', 'City', 'State', 'ZIP', 'Fuel_Type', 'Station_Ph', 'Groups_Wit', 'Access_Day'] 22 | } 23 | }; 24 | -------------------------------------------------------------------------------- /src/app/SidePanel.js: -------------------------------------------------------------------------------- 1 | // NOTE: using jQuery global intead of importing 2 | // because it is included it in vendor.js 3 | // and we don't want it bundled with our application code 4 | /* global $:false */ 5 | import declare from 'dojo/_base/declare'; 6 | import _WidgetBase from 'dijit/_WidgetBase'; 7 | import _TemplatedMixin from 'dijit/_TemplatedMixin'; 8 | // NOTE: we're using Rollup's string plugin to load 9 | // the template which will be inlined into the build output 10 | import template from './templates/SidePanel.html'; 11 | import strings from 'dojo/i18n!./nls/strings'; 12 | 13 | export default declare([_WidgetBase, _TemplatedMixin], { 14 | 15 | baseClass: 'side-panel', 16 | nls: strings, 17 | templateString: template, 18 | 19 | // set panel header title 20 | _setTitleAttr: { node: 'titleNode', type: 'innerHTML' }, 21 | 22 | // wire up events 23 | postCreate () { 24 | this.inherited(arguments); 25 | let domNodeId = '#' + this.domNode.id; 26 | // update chevron icon when panel collapses/expands 27 | $(domNodeId + ' .collapse').on('hide.bs.collapse show.bs.collapse', () => { 28 | $(domNodeId + ' .panel-title .glyphicon').toggleClass('glyphicon-chevron-up glyphicon-chevron-down'); 29 | }); 30 | } 31 | }); 32 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Esri Rollup Example 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 | 25 | 26 | 27 | 28 | 29 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /src/app/mapService.js: -------------------------------------------------------------------------------- 1 | import declare from 'dojo/_base/declare'; 2 | import Map from 'esri/Map'; 3 | import MapView from 'esri/views/MapView'; 4 | import FeatureLayer from 'esri/layers/FeatureLayer'; 5 | import BasemapToggle from 'esri/widgets/BasemapToggle'; 6 | import BasemapToggleVM from 'esri/widgets/BasemapToggle/BasemapToggleViewModel'; 7 | 8 | // define a stateful service to manage the map 9 | const MapService = declare([], { 10 | // create a map and map view 11 | init: function (options) { 12 | this.map = new Map({ 13 | basemap: options.basemap 14 | }); 15 | delete options.basemap; 16 | options.map = this.map; 17 | this.view = new MapView(options); 18 | }, 19 | 20 | createBasemapToggle (node, secondaryBasemap) { 21 | if (!node || !secondaryBasemap || !this.view) { 22 | return; 23 | } 24 | 25 | return new BasemapToggle({ 26 | // Setting widget properties via viewModel is subject to 27 | // change for the 4.0 final release 28 | viewModel: new BasemapToggleVM({ 29 | view: this.view, 30 | secondaryBasemap: secondaryBasemap 31 | }) 32 | }, node); 33 | }, 34 | 35 | // add a feature layer to the map 36 | addFeatureLayer: function (props) { 37 | if (!this.map) { 38 | return; 39 | } 40 | 41 | const featureLayer = new FeatureLayer(props); 42 | this.map.add(featureLayer); 43 | return featureLayer; 44 | } 45 | }); 46 | 47 | // return a singleton instance of this service 48 | if (!_instance) { 49 | var _instance = new MapService(); 50 | } 51 | export default _instance; 52 | -------------------------------------------------------------------------------- /src/app/App.js: -------------------------------------------------------------------------------- 1 | import declare from 'dojo/_base/declare'; 2 | import _WidgetBase from 'dijit/_WidgetBase'; 3 | import _TemplatedMixin from 'dijit/_TemplatedMixin'; 4 | import config from './config'; 5 | import mapService from './mapService'; 6 | import SidePanel from './SidePanel'; 7 | import { formatTitle } from './utils'; 8 | 9 | export default declare([_WidgetBase, _TemplatedMixin], { 10 | 11 | baseClass: 'app', 12 | // using an inline template 13 | templateString: ` 14 |
15 |
16 |
17 |
18 |
19 |
20 | `, 21 | 22 | // kick off app once this component has been created 23 | postCreate () { 24 | this.inherited(arguments); 25 | 26 | // initialize map 27 | config.mapProps.container = this.mapNode; 28 | mapService.init(config.mapProps); 29 | 30 | // initialize side panel 31 | this.sidePanel = new SidePanel({}, this.sidePanelNode); 32 | 33 | // initialize the basemap toggle 34 | this.basemapToggle = mapService.createBasemapToggle(this.basemapToggleNode, config.secondaryBasemap); 35 | 36 | // add feature layer and once loaded 37 | // set side title of side panel 38 | mapService.addFeatureLayer(config.fuelingStationLayerProps).then(layer => { 39 | this.sidePanel.set('title', formatTitle(layer.title || layer.name)); 40 | }); 41 | }, 42 | 43 | // you gotta start me up 44 | startup () { 45 | this.sidePanel.startup(); 46 | this.basemapToggle.startup(); 47 | } 48 | }); 49 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "esri-rollup-example", 3 | "version": "0.1.10", 4 | "description": "Example application using Rollup to bundle local ES2015 modules that use CDN hosted modules from the ArcGIS API for JavaScript", 5 | "main": "src/main.js", 6 | "dependencies": { 7 | "lodash-es": "^4.11.1" 8 | }, 9 | "devDependencies": { 10 | "babel-plugin-external-helpers": "^6.8.0", 11 | "babel-preset-es2015": "^6.16.0", 12 | "browser-sync": "^2.12.3", 13 | "calcite-bootstrap": "^0.3.2", 14 | "del": "^2.2.0", 15 | "gulp": "^3.9.1", 16 | "gulp-concat": "^2.6.0", 17 | "gulp-debug": "^2.1.2", 18 | "gulp-gh-pages": "^0.5.4", 19 | "gulp-if": "^2.0.0", 20 | "gulp-load-plugins": "^1.2.1", 21 | "gulp-rename": "^1.2.2", 22 | "gulp-sass": "^2.2.0", 23 | "gulp-semistandard": "^1.0.0", 24 | "gulp-size": "^2.1.0", 25 | "gulp-sourcemaps": "^2.1.1", 26 | "gulp-uglify": "^2.0.0", 27 | "jquery": "^2.2.3", 28 | "rollup": "^0.36.3", 29 | "rollup-plugin-babel": "^2.6.1", 30 | "rollup-plugin-string": "^2.0.2", 31 | "rollup-plugin-uglify": "^1.0.1", 32 | "semistandard": "^9.1.0" 33 | }, 34 | "scripts": { 35 | "test": "gulp test", 36 | "build": "gulp", 37 | "start": "gulp serve" 38 | }, 39 | "keywords": [ 40 | "ArcGIS", 41 | "JavaScript", 42 | "Rollup" 43 | ], 44 | "author": "Tom Wayson ", 45 | "license": "MIT", 46 | "repository": { 47 | "type": "git", 48 | "url": "git+https://github.com/tomwayson/esri-rollup-example.git" 49 | }, 50 | "bugs": { 51 | "url": "https://github.com/tomwayson/esri-rollup-example/issues" 52 | }, 53 | "homepage": "https://github.com/tomwayson/esri-rollup-example#readme" 54 | } 55 | -------------------------------------------------------------------------------- /src/styles/_appstrap.scss: -------------------------------------------------------------------------------- 1 | // NOTE: copied from: 2 | // ./node_modules/bootstrap-sass/assets/stylesheets/_bootstrap.scss 3 | // and commented out unused modules 4 | 5 | // TODO: uncomment as needed when adding modules 6 | 7 | // Core variables and mixins 8 | @import "bootstrap/variables"; 9 | @import "bootstrap/mixins"; 10 | 11 | // Reset and dependencies 12 | @import "bootstrap/normalize"; 13 | @import "bootstrap/print"; 14 | @import "bootstrap/glyphicons"; 15 | 16 | // Core CSS 17 | @import "bootstrap/scaffolding"; 18 | @import "bootstrap/type"; 19 | @import "bootstrap/code"; 20 | @import "bootstrap/grid"; 21 | @import "bootstrap/tables"; 22 | @import "bootstrap/forms"; 23 | @import "bootstrap/buttons"; 24 | 25 | // Components 26 | @import "bootstrap/component-animations"; 27 | @import "bootstrap/dropdowns"; 28 | // @import "bootstrap/button-groups"; 29 | // @import "bootstrap/input-groups"; 30 | // @import "bootstrap/navs"; 31 | // @import "bootstrap/navbar"; 32 | // @import "bootstrap/breadcrumbs"; 33 | // @import "bootstrap/pagination"; 34 | // @import "bootstrap/pager"; 35 | // @import "bootstrap/labels"; 36 | // @import "bootstrap/badges"; 37 | // @import "bootstrap/jumbotron"; 38 | // @import "bootstrap/thumbnails"; 39 | // @import "bootstrap/alerts"; 40 | // @import "bootstrap/progress-bars"; 41 | // @import "bootstrap/media"; 42 | // @import "bootstrap/list-group"; 43 | @import "bootstrap/panels"; 44 | // @import "bootstrap/responsive-embed"; 45 | // @import "bootstrap/wells"; 46 | // @import "bootstrap/close"; 47 | 48 | // Components w/ JavaScript 49 | // @import "bootstrap/modals"; 50 | // @import "bootstrap/tooltip"; 51 | // @import "bootstrap/popovers"; 52 | // @import "bootstrap/carousel"; 53 | 54 | // Utility classes 55 | // @import "bootstrap/utilities"; 56 | // @import "bootstrap/responsive-utilities"; 57 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Esri Rollup Example Change Log 2 | All notable changes to this project will be documented in this file. 3 | This project adheres to [Semantic Versioning](http://semver.org/). 4 | 5 | ## Unreleased 6 | ## v0.1.9 7 | ### Support 8 | - bump dependencies, add repo to package.json 9 | 10 | ## v0.1.9 11 | ### Changed 12 | - using Rollup to uglify source and generate sourcemaps 13 | - update to JSAPI 4.0 14 | - update to latest rollup and rollup-plugin-uglify to fix build errors 15 | 16 | ### Support 17 | - only bundle app, startup app in index, drop main 18 | 19 | ## v0.1.8 20 | 21 | ### Changed 22 | - only bundle app, startup app in index, drop main 23 | 24 | ## v0.1.7 25 | 26 | ### Added 27 | - added missing `this.inherited(arguments)` to side panel widget 28 | 29 | ### Support 30 | - Use Rollup's `useStrict: false` option instead of gulp-replace on the bundled output. 31 | 32 | ## v0.1.6 33 | 34 | ### Added 35 | - importing capitalize() from ES2015 build of lodash 36 | 37 | ### Support 38 | - better sourcemaps (not inlined) 39 | 40 | ## v0.1.5 41 | 42 | ### Changed 43 | - only include the bootstrap components used by the app 44 | 45 | ## v0.1.4 46 | 47 | ### Added 48 | - added chevron icon to collapsing panel and dynamically update (up/down) using jQuery global 49 | 50 | ### Support 51 | - lint bfore script tasks in live reload 52 | 53 | ## v0.1.3 54 | 55 | ### Added 56 | - added bootstrap JS and jquery for collapsing panel 57 | 58 | ## v0.1.2 59 | 60 | ### Changed 61 | - copy vendor fonts to dist before serving 62 | 63 | ## v0.1.1 64 | 65 | ### Added 66 | added Sass and loading Calcite Bootstrap via Sass 67 | 68 | ### Changed 69 | - minor tweaks to and comments for gulp tasks 70 | - minor tweaks to README 71 | 72 | ## v0.1.0 73 | 74 | ### Added 75 | - added gh-pages deploy 76 | - added gulp build 77 | 78 | ## v0.0.8 79 | 80 | ### Changed 81 | - fleshed out README 82 | 83 | ### Added 84 | - added basemap toggle 85 | - added CHANGELOG 86 | 87 | ## v0.0.7 88 | 89 | ### Added 90 | - re-build whenever src changes 91 | 92 | ## v0.0.6 93 | 94 | ### Changed 95 | - using JSAPI v4 Beta 3 instead of v3.16 96 | - map service can add layer and manages map state 97 | 98 | ## v0.0.5 99 | 100 | ### Added 101 | - added calcite bootstrap and side panel widget 102 | 103 | ## v0.0.4 104 | 105 | ### Added 106 | - added linting w/ semistandard 107 | 108 | ### Added 109 | - added a custom dijit to show current time 110 | 111 | ## v0.0.3 112 | 113 | ### Added 114 | - minify bundled output 115 | 116 | ## v0.0.2 117 | 118 | ### Changed 119 | - babel actually transpiles 120 | 121 | ### Added 122 | - added a custom dijit to show current time 123 | 124 | ## v0.0.1 125 | 126 | ### Added 127 | - first working rollup build! 128 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'); 2 | var gulpLoadPlugins = require('gulp-load-plugins'); 3 | var del = require('del'); 4 | var rollup = require('rollup').rollup; 5 | var rollupBabel = require('rollup-plugin-babel'); 6 | var rollupString = require('rollup-plugin-string'); 7 | var rollupUglify = require('rollup-plugin-uglify'); 8 | var browserSync = require('browser-sync'); 9 | var ghPages = require('gulp-gh-pages'); 10 | 11 | var $ = gulpLoadPlugins(); 12 | var reload = browserSync.reload; 13 | 14 | // NOTE: to debug any task add .pipe($.debug()) after the .src() 15 | 16 | // clean output and temp directories 17 | gulp.task('clean', del.bind(null, ['dist'])); 18 | 19 | // lint source files 20 | gulp.task('lint', function () { 21 | return gulp.src(['./src/app/**/*.js']) 22 | .pipe($.semistandard()) 23 | .pipe($.semistandard.reporter('default', { 24 | breakOnError: !browserSync.active 25 | })); 26 | }); 27 | 28 | // bunlde, minify, and generate sourcemaps 29 | // for app scripts using rollup 30 | gulp.task('scripts:app', function () { 31 | return rollup({ 32 | entry: 'src/app/App.js', 33 | plugins: [ 34 | // compile future ES 2015 to runnable ES 5 35 | rollupBabel({ 36 | runtimeHelpers: true, 37 | // don't compile templates 38 | exclude: 'src/app/templates/**' 39 | }), 40 | // load templates from files 41 | rollupString({ 42 | include: ['**/*.html'] 43 | }), 44 | // minify output 45 | rollupUglify() 46 | ] 47 | }).then(function (bundle) { 48 | return bundle.write({ 49 | // include sourcemaps to ES2015 files 50 | sourceMap: true, 51 | // Dojo friendly output options 52 | format: 'amd', 53 | useStrict: false, 54 | dest: 'dist/app/App.min.js' 55 | }); 56 | }); 57 | }); 58 | 59 | // copy nls files to dist 60 | gulp.task('scripts:nls', function () { 61 | return gulp.src('./src/app/nls/**/*.js') 62 | .pipe(gulp.dest('./dist/app/nls')); 63 | }); 64 | 65 | // concatenate vendor scripts and minify (as needed) 66 | gulp.task('scripts:vendor', function () { 67 | return gulp.src([ 68 | './node_modules/jquery/dist/jquery.js', 69 | // NOTE: include other bootstrap components here as needed 70 | './node_modules/bootstrap-sass/assets/javascripts/bootstrap/transition.js', 71 | './node_modules/bootstrap-sass/assets/javascripts/bootstrap/collapse.js' 72 | ]) 73 | .pipe($.sourcemaps.init()) 74 | .pipe($.concat('vendor.js')) 75 | .pipe(gulp.dest('dist/vendor')) 76 | // also include minified output 77 | .pipe($.uglify()) 78 | .pipe($.rename({ suffix: '.min' })) 79 | .pipe($.sourcemaps.write('.')) 80 | .pipe(gulp.dest('dist/vendor')); 81 | }); 82 | 83 | // copy fonts from vendor dependencies 84 | gulp.task('fonts', function () { 85 | return gulp.src('./node_modules/bootstrap-sass/assets/fonts/bootstrap/**/*.{eot,svg,ttf,woff,woff2}') 86 | .pipe(gulp.dest('dist/styles/fonts')); 87 | }); 88 | 89 | // styles: compile Sass styles to CSS 90 | // TODO: may want to add autoprefixer 91 | // or need to add plumber to handle errors 92 | gulp.task('styles', function () { 93 | return gulp.src('./src/styles/main.scss') 94 | // .pipe($.plumber()) 95 | .pipe($.sourcemaps.init()) 96 | .pipe($.sass.sync({ 97 | outputStyle: 'expanded', 98 | precision: 10, 99 | includePaths: [ 100 | '.', 101 | './node_modules/bootstrap-sass/assets/stylesheets', 102 | './node_modules/calcite-bootstrap/dist/sass' 103 | ] 104 | })) // .on('error', $.sass.logError)) 105 | // .pipe($.autoprefixer({browsers: ['> 1%', 'last 2 versions', 'Firefox ESR']})) 106 | .pipe($.sourcemaps.write('.')) 107 | .pipe(gulp.dest('./dist/styles')); 108 | }); 109 | 110 | // html: for now just copying 111 | // later may want to transform/minify 112 | gulp.task('html', function () { 113 | return gulp.src('./src/*.html') 114 | .pipe(gulp.dest('./dist')); 115 | }); 116 | 117 | // build, copy to dist, and size'r up 118 | gulp.task('build', ['lint', 'fonts', 'scripts:vendor', 'scripts:nls', 'scripts:app', 'styles', 'html'], function () { 119 | return gulp.src('dist/**/*').pipe($.size({ 120 | title: 'build', 121 | gzip: true, 122 | showFiles: true 123 | })); 124 | }); 125 | 126 | // serve up the built application 127 | // and live-reload whenever files change 128 | gulp.task('serve:dist', ['build'], function () { 129 | // serve build output files 130 | browserSync({ 131 | notify: false, 132 | port: 9000, 133 | server: { 134 | baseDir: ['dist'] 135 | } 136 | }); 137 | 138 | // reload whenever output files updated 139 | gulp.watch([ 140 | 'dist/**/*' 141 | ]).on('change', reload); 142 | 143 | // update output files when source files change 144 | gulp.watch('./src/styles/*.scss', ['styles']); 145 | gulp.watch('./src/app/nls/**/*.*', ['lint', 'scripts:nls']); 146 | gulp.watch(['./src/app/**/*.*', '!./src/app/nls/**/*.*'], ['lint', 'scripts:app']); 147 | gulp.watch('./src/*.html', ['html']); 148 | }); 149 | 150 | // remove old files then serve the built application 151 | gulp.task('serve', ['clean'], function () { 152 | gulp.start('serve:dist'); 153 | }); 154 | 155 | // test: for now just lint 156 | // TODO: add tests 157 | gulp.task('test', ['lint']); 158 | 159 | // deploy to github pages 160 | gulp.task('deploy', ['build'], function () { 161 | return gulp.src('./dist/**/*') 162 | .pipe(ghPages()); 163 | }); 164 | 165 | // clean dist and run build 166 | gulp.task('default', ['clean'], function () { 167 | gulp.start('build'); 168 | }); 169 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Esri Rollup Example 2 | 3 | **UPDATE**: This technique demonstrated in this repository will work, but you should probably just use [esri-loader](https://github.com/Esri/esri-loader/). Read this [blog post](http://tomwayson.com/2018/01/05/loader-of-the-things-one-library-to-load-them-all/) to find out why. 4 | 5 | ## Life's Too $hort to Use Dojo Build TM 6 | 7 | **TLDR: An example of a fast, flexible, modern way for you to build *just* your application code locally while using the CDN hosted builds of the [ArcGIS API for JavaScript].** 8 | 9 | This is an example application using [Rollup] to bundle local ES2015 modules that use CDN hosted modules from the [ArcGIS API for JavaScript]. 10 | 11 | You can view the site created by this repo [live](http://tomwayson.github.io/esri-rollup-example/), and see the (unminified) [bundled source code](http://tomwayson.github.io/esri-rollup-example/app/bundle.js). 12 | 13 | This example application also uses [Gulp] to minify the bundled code and manage static assets, but you can [use Rollup with whatever build system you prefer](https://github.com/rollup/rollup/wiki/Build-tools). Earlier versions of the application simply [used npm scripts as a build tool](https://github.com/tomwayson/esri-rollup-example/blob/v0.0.8/package.json#L17-L21). 14 | 15 | ## Getting started 16 | 17 | 1. Clone/download this repository 18 | 2. `cd` into the repository root folder and run 19 | `npm install && npm start` 20 | 3. Make some changes to the source code and the browser will live reload the newly built code 21 | 22 | ## Why? 23 | 24 | Ever waited minutes for a Dojo build only to have it fail? If not, you're doing it wrong. Ever not been able to figure out why it failed? [I have](https://github.com/odoe/generator-arcgis-js-app/issues/11). Even when a Dojo build does work, have you ever looked at the size of the optimized output and realize that you saved exactly 0 bytes over the size of the compact build? There's 4 hours of your life you'll never get back. Ever used the [Web Optimizer](https://jso.arcgis.com/) and wonder why part of your build process involves visiting a web site? 25 | 26 | If you're like me, you're fed up with all that. It's time we respect ourselves and take back our valuable time. Esri's already done the Dojo builds for us and left them on a CDN. We should be able to pull in any of those and just build our own application code. Now with [Rollup], it looks like we can. Even better, we can do so in a fast, flexible, and modern way! 27 | 28 | ### Known benefits 29 | * ~10 million times faster than a Dojo build - something that can be used with live reload! 30 | * No need to bower install half the Internet (i.e. all of Dojo and the [ArcGIS API for JavaScript]) 31 | * No _need_ for bower at all, use either npm or bower, or both, or neither! I'd rather not know about how you handle other people's packages. 32 | * No longer dependent on [grunt-dojo](https://www.npmjs.com/package/grunt-dojo), so use whatever build system you prefer: [Gulp], [npm scripts](http://blog.keithcirkel.co.uk/how-to-use-npm-as-a-build-tool/), or [Grunt](https://github.com/chrisprice/grunt-rollup) - I ain't mad atcha! 33 | * Develop and test off your _built_ code 34 | * Your users' browsers may already have the CDN build of the [ArcGIS API for JavaScript] cached 35 | 36 | ### Potential benefits 37 | These have not (yet) been tested: 38 | * Use other component frameworks ([React](https://github.com/odoe/esrijs4-vm-react), [Web Components](https://github.com/patrickarlt/custom-elements-dev-summit-2016)) and/or UI library ([Bootstrap]) instead of Dijit, again, I'm not mad at you 39 | 40 | ## What's the catch? 41 | 42 | Too good to be true? There a few known limitations that you should understand. However, there are workarounds that might work for you. They work for me! 43 | 44 | ### Known limitations 45 | * Any code to be included in the bundle must use ES2015 imports/exports 46 | * Your app code should be written in ES2015 or [TypeScript](https://github.com/rollup/rollup-plugin-typescript) 47 | * You probably won't be be able to use this with a pre-existing AMD app without something like [amd-to-as6](https://github.com/jonbretman/amd-to-as6) 48 | * See below for options on [handling third party dependencies](#handling-third-party-dependencies) 49 | * Can't use `dojo/text` plugin (but you can [use Rollup's](https://github.com/tomwayson/esri-rollup-example/blob/4bd1b8819b36a009b70f02ba1e0eb82025f072c7/src/SidePanel.js#L4-L6), or [ES2015 template literals](https://github.com/tomwayson/esri-rollup-example/blob/e7f239c5e042ba2fc68d40093a10aa01a6176585/src/app/App.js#L13-L20)) 50 | * Can't inline i18n bundles, but you _can_ [use `dojo/i18n`](https://github.com/tomwayson/esri-rollup-example/blob/4bd1b8819b36a009b70f02ba1e0eb82025f072c7/src/SidePanel.js#L7)! 51 | * Build output is a single AMD module, not a Dojo build layer, so sub modules are not exposed. This should be fine when building apps, but is probably not suitable for building libraries. 52 | 53 | ### Caveats 54 | * Hasn't been used in a production application yet (help me fix that!), so not sure how it scales. 55 | * This is not an [official build solution from Esri](https://developers.arcgis.com/javascript/jshelp/inside_bower_custom_builds.html), so support means [people helping people](https://github.com/tomwayson/esri-rollup-example/issues). 56 | 57 | ## How does it work? 58 | 59 | This approach relies on default behavior of [Rollup] to ignore modules that it can't resolve (i.e. anything in the `esri` or `dojo` packages) and only bundle our application code into a single AMD module. 60 | 61 | ```bash 62 | $ rollup -c rollup-config.js 63 | Treating 'dojo/_base/declare' as external dependency 64 | Treating 'dijit/_WidgetBase' as external dependency 65 | Treating 'dijit/_TemplatedMixin' as external dependency 66 | Treating 'dojo/_base/declare' as external dependency 67 | Treating 'esri/Map' as external dependency 68 | Treating 'esri/views/MapView' as external dependency 69 | Treating 'esri/layers/FeatureLayer' as external dependency 70 | Treating 'esri/widgets/BasemapToggle' as external dependency 71 | Treating 'esri/widgets/BasemapToggle/BasemapToggleViewModel' as external dependency 72 | Treating 'dojo/_base/declare' as external dependency 73 | Treating 'dijit/_WidgetBase' as external dependency 74 | Treating 'dijit/_TemplatedMixin' as external dependency 75 | Treating 'dojo/i18n!./nls/strings' as external dependency 76 | ``` 77 | 78 | Rollup's so smart. We didn't even need to tell it to ignore those modules. 79 | 80 | We can then use the Dojo loader that is included in the [ArcGIS API for JavaScript] to load and run the bundled output like so: 81 | 82 | ```js 83 | // index.html 84 | require(['app/bundle']); 85 | ``` 86 | 87 | ## Handling Third Party Dependencies 88 | 89 | As with the Dojo build you have a few options for how to handle third party libraries. 90 | 91 | ### ES2015 imports/exports 92 | 93 | As stated above, any code to be included in the bundle must use ES2015 imports/exports. If you have a dependency that is written in ES2015, such as [lodash-es](https://www.npmjs.com/package/lodash-es), you can [use import statements from your application modules](https://github.com/rollup/rollup-plugin-typescript) and Rollup will try include only the code (down to the function level) required by your application, a process called [tree shaking](https://medium.com/@Rich_Harris/tree-shaking-versus-dead-code-elimination-d3765df85c80). 94 | 95 | ### Globals 96 | 97 | If a library exposes a global (such as `$`, `_`, `d3`, etc), you can just use the global in your code (no one will get hurt, I promise). You can load the library on the page by including a script tag pointing to either a CDN or [a local file](https://github.com/tomwayson/esri-rollup-example/blob/84b0c433ab37ceca860df2c72b3c412501e7bb97/src/index.html#L26). If using the latter, it is best to first [concatenate all local scripts into a single minified file (vendor.js)](https://github.com/tomwayson/esri-rollup-example/blob/84b0c433ab37ceca860df2c72b3c412501e7bb97/gulpfile.js#L73-L88). I find that most of the libraries I use expose a global, and sometimes I'll use those even with a Dojo build (just to reduce the chances of build errors). 98 | 99 | ### AMD 100 | 101 | If a library only exposes AMD modules and no global (for example [Dojo Bootstrap](http://xsokev.github.io/Dojo-Bootstrap/) or [dojox.charting](https://dojotoolkit.org/reference-guide/1.10/dojox/charting.html)), then unfortunately there's no good way to include that library in this build process. You can still use the library in your application by defining a dojoConfig package that points to either the CDN or local path to the library, but the modules will be loaded asynchronously at runtime. 102 | 103 | ### Considering your choices 104 | 105 | Tree shaking sounds great on paper, but it is [far from perfect](https://github.com/rollup/rollup/issues/45#issuecomment-168127982). I have it working in this example app, but even just pulling in one function (along w/ all of the code Rollup thought might be needed by that function) the bundle [starts to get a little cluttered](http://tomwayson.github.io/esri-rollup-example/app/bundle.js). You can imagine how cluttered the bundle could get if you have a lot of dependencies. In a real app, I'd probably bring in a custom build of lodash as a global. 106 | 107 | Obviously the Dojo build is much better than Rollup at handling AMD-only libraries. That said, I can't think of a single AMD-only library that is better than non-AMD equivalents. Need Bootstrap? Use jQuery. Need charts? Use [C3](http://c3js.org/). Branch out, see how the other half lives. 108 | 109 | ## FAQ 110 | * When should I use this? 111 | * Best suited for developing **new** web **apps** with the [ArcGIS API for JavaScript] with modern tools without the hassles of the Dojo build 112 | * When should I not use this? 113 | * Not suitable for apps that are written in ES5 or use a lot of Dijit and Dojox. 114 | * What versions of the [ArcGIS API for JavaScript] can I use with this? 115 | * This has been tested with v3.16 and v4.0 Beta 3 116 | * Should I use the compact build? 117 | * Depends. You should use the [compact build](https://developers.arcgis.com/javascript/jshelp/intro_accessapi.html#compact-build) unless you are going to use modules that aren't included in it (i.e. `esri/dijit/...`), in which case you should use the full build 118 | * Shouldn't I be able to do the same thing with [webpack](https://webpack.github.io/)? 119 | * Go for it. You can [start here](https://github.com/tomwayson/esri-webpack/tree/es2015). Let me know how that works out for ya. 120 | * I used to (twitter/facebook/read War and Peace/prep for the Bar exam/crochet) while I was waiting for the Dojo build to finish, when can I do that now? 121 | * Now you can spend time on your hobbies after you've shipped! 122 | 123 | Look how happy you could be if you were using Rollup. 124 | 125 | [![Wiz Khalifa - Roll Up Official Music Video](https://img.youtube.com/vi/UhQz-0QVmQ0/0.jpg)](https://www.youtube.com/watch?v=UhQz-0QVmQ0) 126 | 127 | [Rollup]:http://rollupjs.org 128 | [ArcGIS API for JavaScript]:https://developers.arcgis.com/javascript/ 129 | [Gulp]:http://gulpjs.com/ 130 | [Bootstrap]:http://getbootstrap.com/ 131 | --------------------------------------------------------------------------------