Moduly v Javascripte

01/2017 / Milan "perún" Herda / @moriquend / prezentacie.perunhq.org

Časť 3: UMD a natívne moduly v ES2015

UMD

Problém

Som vývojár populárnej knižnice

Problém

Som vývojár populárnej knižnice

  • časť používateľov pracuje v globálnom namespace
  • iná časť má requirejs
  • ďalšia CommonJS moduly

Akým spôsobom budem knižnicu poskytovať?

  • globálna premenná?
  • AMD modul?
  • CommonJS modul?
  • tri rôzne samostatné verzie?

Odpoveď

Všetky verzie v jednom súbore

UMD

Universal Module Definition


(function (root, factory) {
    if (typeof define === 'function' && define.amd) {
        // AMD
        define(['libName'], factory);
    } else if (typeof exports === 'object') {
        // Node, CommonJS-like
        module.exports = factory(require('libName'));
    } else {
        // Browser globals (root is window)
        root.returnExports = factory(root.libName);
    }
}(this, function ($) {
    //    methods
    function myFunc(){};

    //    exposed public method
    return myFunc;
}));
                    

Natívne moduly v ES2015

Export

Príkazom export sa určuje časť modulu, ktorá bude dostupná pre vonkajší svet

Import

Príkaz import sprístupňuje exportované časti modulu.

V Node.js a transpileroch (babel, webpack) zároveň aj vkladá súbor s modulom V prehliadačoch bude potrebné moduly najskôr načítať cez
<script type="module" src="..."></script>

Default export


// module.js

const foo = function () { /*... */};

export default foo;

// main.js

import bar from './module';
                        

Default export/import


// module.js

export default function () { /*... */} // pozor, bez bodkočiarky!

// main.js

import bar from './module';
                        

Pomenované exporty/importy


// module.js

export const PI = 3.14159;

export const square = function (a) {
    return a * a;
};

// main.js

import * as myMath from './module'; // myMath.square, myMath.PI

import { square } from './module';

import { square, PI } from './module';

import { square as mySquare, PI } from './module';

                    

Kombinácie default a pomenovaného importu

Default import musí byť vždy prvý!


// module.js

export const PI = 3.14159;

export default function () { /* ... */ }

export const square = function (a) {
    return a * a;
};

// main.js

import def, { square, PI } from './module';
import def, * as myMath from './module';
                    

Import kvôli vedľajším efektom


import from './module';
                    
Napríklad babel-polyfill

Example time!

Zadanie

  1. Vytvoríme modul obsahujúci definíciu farieb (rgb kód) pre pozadie, text, nadpis, upozornenie a úspech. Každá farba sa bude exportovať pod svojím menom.
  2. Vytvoríme skript, ktorý:
    1. naimportuje všetky farby
    2. naimportuje iba farbu pre nadpis, upozornenie a úspech
    3. naimportuje všetky farby a samostatne farbu pre text

Problém

Node.js zatiaľ nepodporuje import/export z ES2015.

Viac info o problémoch a krátke zhrnutie

Riešenie

Transpilácia cez babel

Príprava

Vytvorenie adresárovej štruktúry


mkdir -p assets/src
mkdir -p assets/dist
                    

Príprava

Nainštalovanie nástrojov


# vytvorenie súboru package.json
npm init

# nainštalovanie gulp a babel ako konzolových nástrojov/príkazov
sudo npm install -g gulp babel-cli

# nainšťalovanie transpilátora ES2015 do ES5
npm install --save-dev babel babel-core babel-preset-es2015

# nainštalovanie task runnera a pluginov
npm install --save-dev gulp gulp-babel run-sequence del
                    

Konfigurácia

.babelrc


{
    "presets": ["es2015"]
}
                    

Konfigurácia

gulpfile.babel.js

Importy a úvodné deklarácie


import gulp        from 'gulp';
import babel       from 'gulp-babel';
import del         from 'del';
import runSequence from 'run-sequence';

const paths = {
    jsSrc: 'assets/src/**/*.js',
    jsDest: 'assets/dist',
};
                    

Konfigurácia

gulpfile.babel.js

Čistenie po predchádzajúcich zbehnutiach


gulp.task('clean', () => {
    return del(paths.jsDest + '/**/*');
});
                    

Konfigurácia

gulpfile.babel.js

Preklad ES2015 do ES5


gulp.task('babel', () => {
    return gulp.src([paths.jsSrc])
        .pipe(babel({
            presets: ['es2015'],
        }))
        .pipe(gulp.dest(paths.jsDest));
});
                    

Konfigurácia

gulpfile.babel.js

Defaultný task


gulp.task('all', (done) => {
    runSequence('clean', 'babel', done);
});

gulp.task('default', ['all']);
                    

Konfigurácia

gulpfile.babel.js

Všetko spolu


import gulp        from 'gulp';
import babel       from 'gulp-babel';
import del         from 'del';
import runSequence from 'run-sequence';

const paths = {
    jsSrc: 'assets/src/**/*.js',
    jsDest: 'assets/dist',
};

gulp.task('clean', () => {
    return del(paths.jsDest + '/**/*');
});

gulp.task('babel', () => {
    return gulp.src([paths.jsSrc])
        .pipe(babel({
            presets: ['es2015'],
        }))
        .pipe(gulp.dest(paths.jsDest));
});

gulp.task('all', (done) => {
    runSequence('clean', 'babel', done);
});

gulp.task('default', ['all']);
                    

Modul

assets/src/module/colors.js


export const BACKGROUND = '#fff';

export const TEXT = '#000';

export const TITLE = '#000080';

export const WARNING = '#800000';

export const SUCCESS = '#008000';

const INFO = 'slateblue';

export default INFO;
                    

Import všetkých farieb

assets/src/all.js


import * as color from './module/colors';

console.log(color.BACKGROUND);
                    

Import niektorých

assets/src/some.js


import { TITLE, WARNING, SUCCESS as COLOR_SUCCESS } from './module/colors';

console.log(TITLE);

console.log(COLOR_SUCCESS);
                    

Import default + textu

assets/src/combination.js


import defaultColor, { TEXT } from './module/colors';

console.log(TEXT);
console.log(defaultColor);
                    

Preklad a spustenie


# preklad
gulp

# spustenie
node assets/dist/all.js
node assets/dist/some.js
node assets/dist/combination.js
                    

Ako je to s implementáciou v prehliadačoch?

Január 2017:

Ako teda písať natívne moduly pre prehliadač?

Písať natívne a transpilovať

Vieme použiť babel spolu s webpack 1

Alebo webpack 2 bez babelu

Ukážka: webpack 1 a babel

Príprava

Inštalácia webpacku


# webpack ako command line príkaz
sudo npm install -g webpack

# webpack do nášho projektu
npm install --save webpack

# plugin pre webpack, aby robil transpiláciu on
npm install --save babel-loader

# gulp-util pre zobrazenie výstupu z webpacku v gulpe
npm install --save gulp-util
                    

Konfigurácia

webpack.config.babel.js


export default {
    entry: {
        all: './assets/src/all.js',
    },
    output: {
        path: 'assets/dist',
        library: '[name]',
        libraryTarget: 'umd',
        filename: '[name].js'
    },
    module: {
        loaders: [
            {
                test: /\.js$/,
                loader: 'babel-loader',
                query: {
                    presets: ['es2015']
                }
            }
        ]
    },
    resolve: {
        extensions: ['', '.js', '.json']
    }
};
                    

Konfigurácia

gulpfile.babel.js

Vyhodíme import babel a task babel


import webpack from 'webpack';
import webpackConfig from './webpack.config.babel';

const paths = {
    jsDest: 'assets/dist',
    finalDest: 'www/assets',
};
                    

Konfigurácia

gulpfile.babel.js

Upravený clean task


gulp.task('clean:dist', () => {
    return del(paths.jsDest + '/**/*');
});

gulp.task('clean:final', () => {
    return del(paths.finalDest + '/**/*');
});

gulp.task('clean', (done) => {
    runSequence('clean:final', 'clean:dist', done);
});
                    

Konfigurácia

gulpfile.babel.js

webpack task


gulp.task('webpack', (done) => {
    let config = Object.create(webpackConfig);

    webpack(config, function (err, stats) {
        if (err) {
            throw new gutil.PluginError('webpack', err);
        }

        gutil.log('[webpack]', stats.toString({
            colors: true,
            progress: true
        }));

        done(); // mimoriadne dôležité, aby gulp vedel, že task skončil
    });
});

                    

Konfigurácia

gulpfile.babel.js

Upravené tasky copy a all


gulp.task('copy', () => {
    return gulp.src([paths.jsDest + '/**/*'])
        .pipe(gulp.dest(paths.finalDest));
});

gulp.task('all', (done) => {
    runSequence('clean', 'webpack', 'copy', done);
});

                    

Web

www/index.html


<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>es2015 moduly cez webpack babel</title>
</head>
<body>

</body>
<script src="assets/all.js"></script>
</html>
                    

Spustenie


# spustit preklad
gulp

# spustit web server
cd www; php -S localhost:8000
                    

Ostáva už len otvoriť prehliadač a nasmerovať ho na localhost:8000

webpack je možné spúšťať aj bez gulpu

Príklad sme videli na workshope o CommonJS moduloch a tak vieme, že stačí upraviť output.path a spustiť príkaz webpack

Ja však svoj dev stack staviam okolo gulpu a chcel som ukázať použitie s gulpom.

Pohrajte sa

Zoberte si unitFactory a extendByRange z prvého workshopu a urobte z nich ES2015 moduly pre web

Zhrnutie

Moduly robíme, pretože:

  • JS kódu je veľa
  • potrebujeme časti s jasnou zodpovednosťou
  • modul je samostatný a tak znovupoužiteľný
  • nechceme prasiť globálny namespace a zvyšovať tak riziko konfliktu názvov

Naučili sme sa

  • platnosť premenných v JS
  • čo je to closure
  • čo je to IIFE
  • ako vyzerá module pattern
  • 3 rôzne implementácie modulov: CommonJS, AMD, UMD, ES2015

Vyskúšali sme si

  • npm a node
  • gulp
  • requirejs a r.js
  • webpack

Videli sme

  • ako robiť štruktúru adresárov pre projekt
  • javascript na strane serveru
  • ako sa moduly riešili kedysi a dnes
  • trochu nového javascriptu

Ďakujem za pozornosť

Otázky?