leifoolsen / es6-komigang

Kom i gang med ES6 ved hjelp av webpack og Babel

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Kom i gang med ECMAScript2015 (ES6) ved hjelp av webpack og Babel

Webpack (the amazing module bundling Swiss army knife) er kort fortalt en pakkehåndterer og et front-end byggesystem som preprosesserer forskjellige webressurser og samler dem i en eller flere statiske pakker som så kan benyttes i klienten. Prosesseringen starter fra et gitt startpunkt (entry), typisk index.js eller main.js. Ut fra startpunktet bygger webpack en avhengighetsgraf basert på filer som er knyttet opp via ìmport, require, url'er i css og href i img tagger. Prosesseringen foregår via såkalte "loadere" - ganske likt "tasks" i andre byggeverktøy, som Gulp.

Ved hjelp av Babel transformeres es6 til es5, som de fleste moderne nettlesere kan kjøre.

Hva trenger vi

Opprett et prosjekt og installer webpack og Babel

NodeJS må være installert på forhånd og det forutsettes at du har grunnleggende kunnskap om NodeJS.

Prosjektstruktur

mkdir es6-komigang
cd es6-komigang
mkdir src
mkdir src/js
mkdir src/js/components
npm init -y

Prosjektstruktur dersom du følger hele eksemplet

|
+-- src
|   +-- html
|   +-- js
|   |   +-- components
|   +-- stylesheets
|   |   +-- base
|   |   +-- components
|   |   +-- layout
|   |   +-- pages
|   |   +-- themes
|   |   +-- utils
|   |   +-- vendor
+-- test
|   +-- js
|   |   +-- components
+-- stub-server
|   +-- data
+-- config

Installer nødvendige pakker

# webpack
npm install --save-dev webpack webpack-dev-server

# babel-core and babel-loader
npm install --save-dev babel-core babel-loader

# ES2015/ES6
npm install --save-dev babel-preset-es2015

# ES7
npm install --save-dev babel-preset-stage-0

# Runtime support
npm install --save babel-polyfill
npm install --save babel-runtime
npm install --save-dev babel-plugin-transform-runtime

# ES6 Promise, for node < v4
npm install --save dev es6-promise

# 3'dje parts bibliotek. Benyttes for å demonstrere splitting av JavaScript
npm install --save moment

Dette gir følgende package.json i prosjektkatalogen:

{
  "name": "es6-komigang",
  "version": "0.0.1",
  "description": "Kom i gang med ES6 ved hjelp av webpack og Babel",
  "main": "webpack.config.js",
  "dependencies": {
    "babel-polyfill": "^6.2.0",
    "babel-runtime": "^6.2.0",
    "moment": "^2.10.6"
  },
  "devDependencies": {
    "babel-core": "^6.2.1",
    "babel-loader": "^6.2.0",
    "babel-plugin-transform-runtime": "^6.1.18",
    "babel-preset-es2015": "^6.1.18",
    "babel-preset-stage-0": "^6.1.18",
    "es6-promise": "^3.0.2",
    "webpack": "^1.12.9",
    "webpack-dev-server": "^1.14.0"
  },
  "scripts": {
    "dev": "./node_modules/.bin/webpack-dev-server --hot --inline --module-bind --progress --color",
    "build": "./node_modules/.bin/webpack",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [
    "webpack", "babel", "es6"
  ],
  "author": "LOL",
  "license": "ISC"
}

Opprett webpack konfigurasjonsfil, webpack.config.js

if (!global.Promise) {
  global.Promise = require('es6-promise').polyfill();
}
const webpack = require('webpack');
const path = require('path');

module.exports = {
  debug: true,
  cache: true,
  devtool: 'eval-source-map',
  entry: {
    app: [
      'babel-polyfill',
      path.join(__dirname, 'src/main.js') 
    ],
    vendor: [ 
      'moment'
    ]
  },
  output: {
    path             : path.join(__dirname, 'dist'),
    filename         : '[name].js',
    sourceMapFilename: '[file].map',
    pathinfo         : true,
    publicPath       : '/static/'
  },
  resolve: {
    extensions: ['', '.webpack.js', '.web.js', '.js', '.jsx', '.css', '.scss']
  },
  module: {
    loaders: [
      {
        test: /\.js[x]?$/,                    
        include: path.join(__dirname, "src"), 
        loader: 'babel-loader',
        query: {                              
          plugins: ['transform-runtime'],
          presets: ['es2015', 'stage-0']
        }
      }
    ]
  },
  plugins: [
    new webpack.optimize.CommonsChunkPlugin({
      name: 'vendor',
      minChunks: Infinity
    })
  ],
  devServer: {
    contentBase: './src',
    port: 8080
  }
};

En god beskrivelse av hvordan man setter opp Babel sammen med webpack finner du bl.a. her: Using ES6 and ES7 in the Browser, with Babel 6 and Webpack

Lag filen ./src/index.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
    <title>webpack ES6 demo</title>
  </head>
  <body>
    <div id="container">
    </div>
    <script type="text/javascript" src="/static/vendor.js" charset="utf-8"></script>
    <script type="text/javascript" src="/static/app.js" charset="utf-8"></script>
  </body>
</html>

Lag filen ./src/components/Person.js

'use strict';
class Person {
  constructor(_first, _last) {
    this._first = _first;
    this._last = _last;
  }
  get name() {
    return this.first + ' ' + this.last;
  }
  toString() {
    return this.name;
  }
}
export default Person;

Lag filen ./src/main.js

'use strict';

import moment from 'moment';
import Person from './js/components/Person.js';

class App {
  const run() {
    const element = document.querySelector('#container');
    const person = new Person('Leif', 'Olsen');
    const content = document.createElement('h1');
    content.classList.add('Person');
    content.textContent = `${moment().format('YYYY-MM-DD HH:mm:ss')}: Yo ${person}`;
    element.appendChild(content);
  }
}
document.addEventListener('DOMContentLoaded', () => App.run());

Prøv ut koden

  • Åpne et terminalvindu og start utviklingsserveren med følgende kommando: npm run dev
  • Åpne nettleseren og naviger til: http://localhost:8080/webpack-dev-server/
  • Eventuelle endringer i koden kan du observere fortløpende i terminalvinduet og i nettleseren.
  • Stopp serveren med Ctrl+C
  • Kjør kommandoen npm run build og verifiser at ./distkatalogen inneholder filene vendor.js og app.js

Dette er i hovedsak utviklingsmiljøet du trenger for å komme i gang med ECMAScript 2015, ES6.

Resten av eksemplet forutsetter at du benytter Node-4.x eller Node-5.x!

Polyfills og Shims

TODO

Konfigurernig av Node og Webpack vha node-config

TODO

Rest-api med Node Express

Dette avsnittet viser hvordan man kan sette opp Node Express i et ES6-miljø og hvordan man setter opp en proxy fra webpack dev server til Node Express slik at man enkelt kan prøve ut ES6 fetch-api'et.

....... Ved koding av frontend kan man benytte Node Express som rest-api stubserver. ....... TODO

Forbedret arbeidsflyt med kodeanalyse, enhetstester og prosessering av statiske ressurser

I de neste avsnittene viser jeg hvordan man kan legge til flere nyttige verktøy.

EsLint

Kontinuerlig kodeanalyse for å avdekke potensielle problemer er greit å ha i arbeidsflyten. Til det trenger vi følgende:

npm install --save-dev eslint eslint-loader babel-eslint

Legg til følgende kode i webpack.config.js

preLoaders: [
  {
    test: /\.js[x]?$/,
    include: [
      path.join(__dirname, 'src'),
      path.join(__dirname, 'test')
    ],
    loaders: ['eslint']
  }
],

I dette eksemplet konfigureres EsLint i webpack sin konfigurasjonsfil. Legg til følgende kode i ./webpack.config.js

eslint: {
  'parser': 'babel-eslint',
  'env': {
    'browser': true,
    'node': true,
    'es6': true
  },
  'settings': {
    'ecmascript': 7,
    'jsx': true
  },
  'rules': {
    'strict': 0,
    'no-unused-vars': 2,
    'camelcase': 1,
    'indent': [1, 2],
    'quotes': 0,
    'linebreak-style': [2, 'unix'],
    'semi': [2, 'always']
  }
}

Linting av koden skjer kontinuerlig neste gang testserveren startes opp.

Statiske ressurser: HTML, CSS/SASS, fonter og grafiske elementer

Til prosessering av CSS/SASS og grafiske elementer trenger vi følgende.

npm install --save-dev html-loader
npm install --save-dev style-loader
npm install --save-dev css-loader
npm install --save-dev sass-loader
npm install --save-dev node-sass
npm install --save-dev file-loader
npm install --save-dev url-loader
npm install --save-dev extract-text-webpack-plugin
npm install --save-dev autoprefixer postcss-loader
npm install --save-dev resolve-url-loader  
npm install --save-dev autoprefixer  

Legg til følgende kode i ./webpack.config.js for å håndtere statiske ressurser.

if (!global.Promise) {
  global.Promise = require('es6-promise').polyfill();
}
const path = require('path');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const autoprefixer = require('autoprefixer');

const cssLoader = [
  'css-loader?sourceMap',
  'postcss-loader'
].join('!');

const sassLoader = [
  'css-loader?sourceMap',
  'postcss-loader',
  'resolve-url-loader',
  'sass-loader?sourceMap&expanded'
].join('!');

module.exports = {
  entry: {
    app: [
      path.join(__dirname, 'src/main.scss'), // Styles
      'babel-polyfill',                      // Babel requires some helper code to be run before your application
      path.join(__dirname, 'src/main.js')    // Add your application's scripts last
    ],
    vendor: [
      'moment'
    ]
  },
  ....
  devtool: 'eval-source-map',
  loaders: [
    ....
    {
      test: /\.html$/,
      include: path.join(__dirname, 'src/html'),
      loader: "html-loader"
    },
    {
      test: /\.scss$/,
      include: path.join(__dirname, 'src'),
      loader: ExtractTextPlugin.extract('style-loader', sassLoader)
    },
    {
      test: /\.css$/,
      include: path.join(__dirname, 'src'),
      loader: ExtractTextPlugin.extract('style-loader', cssLoader)
    },
    // Images
    // inline base64 URLs for <=16k images, direct URLs for the rest
    {
      test: /\.jpg/,
      loader: 'url-loader',
      query: {
        limit: 16384,
        mimetype: 'image/jpg'
      }
    },
    { test: /\.gif/, loader: 'url-loader?limit=16384&mimetype=image/gif' },
    { test: /\.png/, loader: 'url-loader?limit=16384&mimetype=image/png' },
    { test: /\.woff(2)?(\?v=[0-9]\.[0-9]\.[0-9])?$/, loader: "url-loader?limit=16384&minetype=application/font-woff" },
    { test: /\.(ttf|eot|svg)(\?v=[0-9]\.[0-9]\.[0-9])?$/, loader: "file-loader?limit=16384" }
  ],
  plugins: [
    new webpack.optimize.CommonsChunkPlugin({
      name: 'vendor',
      minChunks: Infinity
    }),
    new ExtractTextPlugin('styles.css', {
			disable: false,
			allChunks: true
		})
  ],
  postcss: [
    autoprefixer({
      browsers: ['last 3 versions']
    })
  ],
  ....  
}

Loadere evalueres fra høyre mot venstre: SCSS-filer kompileres med SASS, deretter kjører autoprefixer, så produseres en CSS-fil; ./dist/styles.css. CSSfilen produseres på bakgrunn av de SASS/CSS-modulene som importeres i ./src/main.scss og hvilke SASS/CSS-moduler som refereres i JavaScriptkoden; Person.js og Person.scss. Fordelen med en CSSfil generert av webpack er at produsert CSSfil kun inneholder kode som man faktisk bruker. Hvordan dette foregår i praksis er godt forklart i artikkelen Smarter CSS builds with Webpack. CSS-strukturen som benyttes i dette eksemplet er omtalt i Sass Guidelines, The 7-1 Pattern. Det meste ev SASSkoden er hentet fra sass-boilerplate som følger 7-1 mønsteret.

Oppdater filen ./src/index.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
    <title>webpack ES6 demo</title>
    <link rel="stylesheet" href="/static/styles.css" />
  </head>
  <body>
    <div id="container" class="container">
    </div>
    <script type="text/javascript" src="/static/vendor.js" charset="utf-8"></script>
    <script type="text/javascript" src="/static/app.js" charset="utf-8"></script>
  </body>
</html>

Restart serveren (Ctrl+C, deretter npm run dev)

Lag filen ./src/html/header.html

<div class="header">
  <h2>Header ...</h3>
</div>

Lag filen ./src/html/footer.html

<div class="footer">
  <h3>Footer ...</h3>
</div>

Organiseringen av SASS-koden bør følge 7-1 mønsteret nevnt i Sass Guidelines.

|
+-- src/
|   +-- main.scss
|   +-- stylesheets/
|   |   +-- base/
|   |   +-- components/
|   |   +-- layout/
|   |   +-- pages/
|   |   +-- themes/
|   |   +-- utils/
|   |   +-- vendor

Lag filen ./src/main.scss

@charset 'UTF-8';

// 1. Configuration and helpers
// No global Sass context, as suggested by http://bensmithett.com/smarter-css-builds-with-webpack/
// Only import what is strictly needed

// 2. Vendors
@import
  'stylesheets/vendor/normalize.css';

// 3. Base stuff
@import
  'stylesheets/base/base',
  'stylesheets/base/typography',
  'stylesheets/base/helpers';

// 4. Layout-related sections
@import
  'stylesheets/layout/header',
  'stylesheets/layout/footer';

// 5. Components

// 6. Page-specific styles

// 7. Themes
@import
  'stylesheets/themes/default';

Lag filen ./src/stylesheets/layout/_header.scss

.header {
  padding: 10px 0;
  background-color: silver;
}

Lag filen ./src/stylesheets/layout/_footer.scss

.footer {
  background-color: LightSteelBlue;
  padding-top: 1px;
  border-bottom: 3px solid #000;
}

Lag filen ./src/stylesheets/themes/_default.scss

html, body {
  position: relative;
  height: 100%;
  min-height: 100%;
}
body {
  background-color: green;
}
.container {
  background-color: yellow;
  height: 100%;
  min-height: 100%;
}

Kopier normalize.css fra normalize.css til mappen ./src/stylesheets/vendor/

Kopier _variables.css fra sass-boilerplate til mappen ./src/stylesheets/utils/

Kopier _mixins.css fra sass-boilerplate til mappen ./src/stylesheets/utils/

Kopier _base.scss fra sass-boilerplate til mappen ./src/stylesheets/base/. Legg til følgende i toppen av fila:

@import 'stylesheets/utils/variables';
@import 'stylesheets/utils/mixins';

Kopier _helpers.scss fra sass-boilerplate til mappen ./src/stylesheets/base/. Legg til følgende i toppen av fila:

@import 'stylesheets/utils/variables';

Kopier _typography.scss fra sass-boilerplate til mappen ./src/stylesheets/base/. Legg til følgende i toppen av fila:

@import 'stylesheets/utils/variables';

Last ned et ikon fra f.eks. findicons til mappen ./src/components/ og omdøp filen til smiley.png.

Lag filen ./src/js/components/Person.scss

.Person {
  background-image: url('smiley.png');
  background-repeat: no-repeat;
  background-position: 4px center;
  background-size: auto 90%;
  background-color: white;
  padding-left: 54px;
}

Oppdater filen ./src/compoments/Person.js

'use strict';

import './Person.scss';

class Person {
  constructor(_first, _last) {
    this._first = _first;
    this._last = _last;
  }
  get name() {
    return this.first + ' ' + this.last;
  }
  toString() {
    return this.name;
  }
}
export default Person;

Oppdater filen ./src/main.js

'use strict';

import moment from 'moment';
import Person from './js/components/Person.js';

// Get header html, using import
import header from './html/header.html';

// Get footer html, using require
const footer = require('./html/footer.html');

class App {
  const run() {
    const element = document.querySelector('#container');
    const person = new Person('Leif', 'Olsen');

    // content
    const content = document.createElement('h1');
    content.classList.add('Person');
    content.textContent = `${moment().format('YYYY-MM-DD HH:mm:ss')}: Yo ${person}`;
    element.appendChild(content);

    // Append header
    content.insertAdjacentHTML('beforebegin', header);

    // Append footer
    content.insertAdjacentHTML('afterend', footer);
  }
}

// Start
document.addEventListener('DOMContentLoaded', () => App.run());

Dersom testserveren kjører kan du overvåke resultatet av kodeendringene i nettleseren.

ES6 enhetstester med Karma og Jasmine

Enhetstester er jo egentlig feigt - men noen ganger er det helt ålreit å ha dem ;-) Oppsett av testmiljø med Karma, Jasmine og PhantomJS er som følger.

npm install phantomjs jasmine jasmine-core karma karma-jasmine karma-phantomjs-launcher karma-sourcemap-loader karma-coverage karma-spec-reporter karma-webpack null-loader --save-dev

Lag filen ./karma.conf.js

var path = require('path');
module.exports = function(config) {
  config.set({
    basePath: '',
    browsers: ['PhantomJS'],
    frameworks: ['jasmine'],
    files: [
      './test/test-context.js'
    ],
    plugins: [
      'karma-webpack',
      'karma-jasmine',
      'karma-sourcemap-loader',
      'karma-phantomjs-launcher'
    ],
    preprocessors: {
      './test/test-context.js': [ 'webpack', 'sourcemap' ]
    },
    reporters: ['progress'],
    colors: true,
    logLevel: config.LOG_INFO,
    autoWatch: true,
    webpack: {
      devtool: 'eval-source-map',
      module: {
        loaders: [
          {
            test: /\.(png|jpg|gif|woff|woff2|css|sass|scss|less|styl)$/,
            loader: 'null-loader'
          },
          {
            test: /\.js[x]?$/,
            include: [
              path.join(__dirname, 'src'),
              path.join(__dirname, 'test')
            ],
            loader: 'babel-loader',
            query: {
              plugins: ['transform-runtime'],
              presets: ['es2015', 'stage-0']
            }
          }
        ]
      },
      watch: true
    },
    webpackServer: {
      noInfo: true
    },
    webpackMiddleware: {
      noInfo: true
    }
  });
};

Lag filen ./test/test-context.js

'use strict';
var context = require.context('.', true, /(-spec\.js$)|(Test\.js$)/);
context.keys().forEach(context);
console.log(context.keys());

Lag filen ./test/js/components/Person-spec.js

'use strict';
import Person from '../../../src/js/components/Person';
describe('Person', () => {
   it('should say hello to leif', () => {
       let person = new Person('Leif', 'Olsen');
       expect(person.name).toBe('Leif Olsen');
   });
});

Oppdater "scripts"-blokken i ./package.json.

"scripts": {
  "dev": "./node_modules/.bin/webpack-dev-server --hot --inline --module-bind --progress --color",
  "build": "./node_modules/.bin/webpack",
  "test": "./node_modules/.bin/karma start"
},

Kjør testene: npm test

Testene kjøres initielt. Deretter kjøres testene så snart Karma oppdager endringer i koden.

Avslutt testovervåkingen med Ctrl+C

TODO: Vurder Mocha, Chai og JsDom i stedet for Karma? Se: From Karma to Mocha, with a taste of jsdom, Webpack testing, A modern React starter pack based on webpack

React

For å komme i gang med React trenger du som et minimum:

npm istall --save react react-dom
npm install --save-dev babel-preset-react

Og babel-loader i webpack.config.js blir da:

{
  test: /\.js[x]?$/,                     // Only run `.js` and `.jsx` files through Babel
  include: path.join(__dirname, 'src'),  // Skip any files outside of your project's `src` directory
  loader: 'babel-loader',
  query: {                               // Options to configure babel with
    plugins: ['transform-runtime'],
    presets: ['es2015', 'stage-0', 'react']
  }
},

Nyttige lenker

ES6

events, EventEmitter, PubSub

RxJS

Webpack

CSS/SASS

Lint

Test

NoeJS, Express

Videos

Etc

About

Kom i gang med ES6 ved hjelp av webpack og Babel

License:ISC License


Languages

Language:JavaScript 55.2%Language:CSS 41.2%Language:HTML 3.6%