Doruk Kutlu
JavaScript Geliştirici
@doruk
@d0ruk
ES2015'e kadar, JS'in standartlaşmış bir modül yükleme sistemi yoktu.
Revealing Module
var revealingModule = (function () {
// this === window
var private = "yassah",
public = "selam";
function privateFn() { return private; }
return {
setPrivate(val) { private = val; },
getPrivate: privateFn,
public,
};
})();
console.log(revealingModule.private); // undefined
console.log(revealingModule.getPrivate()); // yassah
revealingModule.setPrivate("mahrem");
console.log(revealingModule.getPrivate()); // mahrem
console.log(revealingModule.public); // selam
revealingModule.public = 42;
console.log(revealingModule.public); // 42
CommonJS
// b.js
exports = 42; // bir etkisi yok
// a.js
var b = require("./b");
console.log(b); // {}
module.exports = 42;
var b = require("./b");
console.log(b); // 42
exports.meaning = 42; // module.exports overrides
var b = require("./b");
console.log(b); // { meaning: 42 }
Asynchronous Module Definition
Asenkron modül yükleme amacıyla yaratılan CommonJS fork'u
define("alias", ["b.js", "c.js"], function (b, c) {
// ya da define(function(require) { var b = require("b.js") })
// b.js ve c.js'den export edilenler scope'ta
return {...};
});
-- index.html
-- src/
-- index.js
-- require.js
<script data-main="src/index" src="require.js"></script>
require(["alias"], function(alias) {
// Eğer "alias" da require() ile asenkron yükleme yapıyorsa
// o bitene kadar bekledikten sonra bu noktaya geliyor
});
Universal Module Definition
(function (this, cb) {
if (typeof define === "function" && define.amd) {
define(["jquery"], cb); // AMD
} else if (typeof exports === "object") {
module.exports = cb(require("jquery")); // CJS
} else {
this.myModule = cb(this.jQuery); // browser global
}
}(window, function($) {
return $(document.body);
}));
Modül yükleme işlemi için belirlenmiş pattern'lar topluluğu
Şurada örnekler bulabilirsiniz.
ECMAScript standardında tanımlanan modül yükleme yöntemi
Modüller dosyalara bölünmüştür. Her dosyada en fazla bir tane modül bulunabilir.
// exports.js
export function bar() { return "bar" }
export default "foo"
export const baz = "baz"
// imports.js
import def from "./exports" // def === "foo"
import * as all from "./exports"
// all -> { default: "foo", bar: function(){...}, baz: "baz" }
import def, { bar, baz } from "./exports"
Her modülde en fazla 1 tane default export olabilir. (hiç olmayabilir)
Terminalden komutlar vererek konfigüre edebildiğiniz, JS ile yazılmış bir compiler'dır.
> webpack -p --entry "./src/index" --progress --hide-modules
Ancak, terminale uzun bir
komut girmek yerine,
bir config objesi vermek daha mantıklı.
// webpack.config.js
module.exports = function(env) {
return { // config objesi
entry: // modül ağına giriş
output: // nereye ve nasıl çıktı alınacak
module: // karşılaşılan dosyalara nasıl müdahale edilecek
plugins: // build prosesinize eklentiler
}
}
Girdi: kaynak kodunuz (modüller ağı) ve konfigürasyonunuz
Çıktı: emit edilen fiziksel dosyalar
define(["a", "b"], function(){ ... });
require("./index.css");
const svg = require("./static/resim.svg");
const jpeg = require("./static/resim2.jpeg");
const foo = require("./bar.js)";
// ya da ES2015 import
import foo from "./bar.js"
@import url("https://fonts.googleapis.com/css?family=Roboto");
@font-face {
font-family: "Roboto";
src: url("/fonts/Roboto-Regular.ttf") format("truetype");
}
Mutabık mıyız?
> node -v # 4 ve üzeri
> npm -v # 3 ve üzeri
> git --version # varlığı yeter
> mkdir webpack-test && cd webpack-test
> npm init -y
> npm i -D webpack # ~30sn
./package.json
"scripts": {
"start": "webpack",
"watch": "webpack -w",
}
./webpack.config.js (ya da webpackfile.js)
const path = require("path");
module.exports = function(env) {
return {
context: path.resolve(__dirname, "src"),
entry: ["./index.js"],
output: {
filename: "bundle.js",
path: path.resolve(__dirname, "build"),
}
}
}
> mkdir src && cd src
./src/index.js
console.dir(navigator)
> npm start./build/index.html
<body><script src="bundle.js"></script></body>
> npm i -D css-loader style-loader
webpack.config.js
{
entry: /**************/,
output: /**************/,
module: {
rules: [
{
test: /\.css$/,
include: path.resolve(__dirname, "src"),
loaders: ["style-loader", "css-loader"]
}
]
},
}
./src/index.css
body {
background: blue;
}
./src/index.js
require("./index.css");
console.dir(navigator);
> npm start> rm build/index.html
> npm i -D html-webpack-plugin
webpack.config.js
const HtmlWebpackPlugin = require("html-webpack-plugin");
module.exports = function(env) {
return {
entry: /*****************/,
output: /*****************/,
module: /*****************/,
plugins: [new HtmlWebpackPlugin()],
}
}
> npm run watch
> npm i <herhangi bir şey>
index.js
const lib = require("herhangi")
> git clone https://github.com/d0ruk/webpack-sunum
> cd webpack-sunum
> git checkout mvp
> npm i
{
entry: {
"chunkName1": "./src/index.js", // string | [string]
"chunkName2": "./src/index.amd.js"
},
}
module: {
rules: [
{
test: /\.css$/,
include: path.resolve(__dirname, "src"),
use: [
{ loader: "style-loader" },
{ loader: "css-loader" },
]
}
]
}
{
output: {
filename: "[name]_[hash].js", // [hash] [chunkhash]
path: path.resolve(__dirname, "build"),
publicPath: "//cdn.example.com/[hash]",
}
}
hesaplanan hash default 20 karakter
[hash:5] gibi limitlenebiliyor
module: {
rules: [{
test: /\.css$/,
use: [
{ loader: "style-loader" },
{
loader: "css-loader",
options: { importLoaders: 1 }
},
{
loader: "less-loader",
options: { noIeCompat: true }
}
]
}]
}
Config objesinin çoğu bu 4 key altında tanımlanıyor
return {
entry: { "chunkName": [String] },
output: {
filename: String,
path: String
},
module: {
rules: [{
test: RegExp,
use: [{ loader: String, options: {} }, {}, {}],
}, {}, {}]
},
plugins: []
}
const ExtractTextPlugin = require("extract-text-webpack-plugin");
{
test: /\.css$/,
include: path.resolve(__dirname, "src"),
use: ExtractTextPlugin.extract({
fallback: "style-loader",
use: [
{ loader: "css-loader" },
]
})
}
plugins: [
new HtmlWebpackPlugin(),
new ExtractTextPlugin({ filename: "styles.css" }),
]
{ filename: "[name]_[hash:5].css" } // [id] [contenthash]
her compilation'da farklı isimde bir dosya emit ediyor
const CleanWebpackPlugin = require("clean-webpack-plugin");
return {
plugins: [
new CleanWebpackPlugin([path.resolve(__dirname, "build/**.**")]),
]
}
module: {
rules: [
/************************/
{
test: /\.js$/,
include: path.resolve(__dirname, "src"),
use: [
{
loader: "babel-loader",
options: { presets: ["react", "env"] }
}
]
}
]
}
./src/index.js
import "./index.css"
import React from "react"
import ReactDOM from "react-dom"
const Component = () => Merhaba Dünya
;
ReactDOM.render(<Component />, document.body);
ReactDOM.render(<Component />, document.getElementById("app"));
new HtmlWebpackPlugin({
title: "Taytıl",
template: "template.html" // relative to global context
})
./src/template.html
<head>
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
<div id="app"></div>
</body>
const autoprefixer = require("autoprefixer");
return {
// ......
{
test: /\.css$/,
use : ExtractTextPlugin.extract({
// ......
use: [
// ......
{
loader: "postcss-loader",
options: { plugins: [autoprefixer] }
}
]
})
},
}
./src/index.css
#app { box-shadow: 10px 5px 5px green; }
const Component = () => <h1><b>Merhaba Dünya</b></h1>;
h1 b { color: red; }
h1 {
& b {
color: red;
}
}
> npm repo postcss-nesting
module: {
rules: [
{
test: /.svg$/,
include: path.resolve(__dirname, "src"),
use : [
{ loader: "url-loader" }
]
}
]
}
options: {
limit: 10 * 1024,
name: "[name].[hash:5].[ext]",
}
index.js
import logo from "./webpack-logo.svg"
const Component = () => (
Merhaba Dünya
);
> ./node_modules/.bin/webpack --env merhaba
webpack.config.js
module.exports = env => {
console.log(env); // merhaba
return {};
}
yani
module.exports = env => {
const isDev = env === "development";
return {};
}
> git checkout voltran
> npm i
> webpack --config myConfig.js
const arr = [1, 2, 3];
const arr2 = arr.concat(true ? [4] : [5]); // [1, 2, 3, 4]
const arr3 = arr.concat(false ? [4] : [5]); // [1, 2, 3, 5]
module.exports = function(env) {
const isDev = env === "development";
const toAppend = isDev
? []
: [new CleanWebpackPlugin([path.resolve(__dirname, "build/**.**")])];
return {}
}
output: {
filename: isDev
? "[name].js"
: "[name]_[hash:5].js",
path: path.resolve(__dirname, "build"),
}
plugins: [
/*******************/
new ExtractTextPlugin({
filename: isDev
? "[name].css"
: "[name]_[hash:5].css",
}),
].concat(toAppend)
> webpack -p
===
> webpack --optimize-minimize --define process.env.NODE_ENV="production"
===
{
module: /*****************/,
plugins: [
new webpack.optimize.UglifyJsPlugin(),
new webpack.DefinePlugin({
process: {
env: { NODE_ENV: JSON.stringify("production") }
}
}),
],
}
return {
plugins: [
/*******************/,
new webpack.DefinePlugin({
process: {
env: {
NODE_ENV: isDev
? JSON.stringify("development")
: JSON.stringify("production")
}
}
}),
]
}
index.js
const Component = () => (
{JSON.stringify(process, null, 2)}
);
[new CleanWebpackPlugin(/**************/),
new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: false,
drop_console: true,
dead_code: true,
unused: true,
},
mangle: false,
comments: false,
}),
]
package.json
"scripts": {
"start": "webpack --progress --hide-modules",
"watch": "webpack -w --progress --hide-modules --env development"
}
plugins: [new webpack.optimize.CommonsChunkPlugin(options)]
{
name: String, // ya da names: [String]
filename: String, // default'u output.filename
minChunks: Number|Infinity|function(module, count) -> boolean,
}
entry: {
"home": "./home.js",
"about": "./about.js",
},
plugins: [
new HtmlWebpackPlugin({
title: "Home",
template: "template.html",
filename: "home.html",
}),
new HtmlWebpackPlugin({
title: "About",
template: "template.html",
filename: "about.html",
}),
]
plugins : [
new webpack.optimize.CommonsChunkPlugin({ name: "commons" }),
]
plugins: [
new webpack.optimize.CommonsChunkPlugin("commons"),
]
tomato tomato
new webpack.optimize.CommonsChunkPlugin({
name: "commons",
minChunks: 2
})
new webpack.optimize.CommonsChunkPlugin({
name: "commons",
minChunks: function(m, cnt) {
return cnt === 2;
}
})
tomato tomato
minChunks: function(m, cnt) {
return m.context && m.context.includes("node_modules");
}
bak çok ilginç
{ name: ["commons", "asdawd"] }
pop quiz
node_modules'da olanları bir chunk'a,
runtime kodunu bir başka chunk'a koy
new webpack.optimize.CommonsChunkPlugin({
name: "bootstrap",
minChunks: (m, cnt) => !m.context
})
middleware'la webpack compilation yapan
node.js http(s) server
"scripts": {
"start": "webpack -w --progress --hide-modules",
"dev": "webpack-dev-server",
"build": "webpack --progress --hide-modules --env production"
},
return {
entry: /*************/,
output: /*************/,
devtool: "source-map"
}
class Component extends React.Component {
constructor() {
super();
setTimeout(() => { throw new Error("boom"); }, 2000);
}
render() {}
}
"dev": "webpack-dev-server --env development",
devtool: isDev ? "eval-source-map" : "source-map",

Hot Module Replacement
aktif etmek için 3 şey gerekir
modülün HMR kabul etmesi
if(module.hot) {
module.hot.accept();
}
config'de devServer
return {
entry: /*************/,
output: /*************/,
devServer: {
port: 9000,
open: true,
hot: true,
overlay: true,
stats: "normal",
}
}
stats: {
assets: true,
chunks: false,
colors: true,
entrypoints: true,
modules: false,
timings: true,
}
.hot-update chunk'larını oluşturan plugin
const toAppend = isDev
? [new webpack.HotModuleReplacementPlugin(),
new webpack.NamedModulesPlugin()]
: [new CleanWebpackPlugin(/*************/),]
> npm run dev
CSS hot update ediyor mu?
new ExtractTextPlugin({
filename: isDev
? "[name].css"
: "[name]_[hash:5].css",
disable: isDev,
})
{
loader: "babel-loader",
options: {
presets: ["react", "env"],
plugins: [require("babel-plugin-syntax-dynamic-import")],
} // v7.x syntax
}
webpack.config.js
entry: {
"main": "./index.js",
// "ticker": "./Ticker.js"
},
index.js
import("./Ticker.js")
.then(m => {
ReactDOM.render(<Component />, document.getElementById("app"));
ReactDOM.render(<m.default />, document.getElementById("app2"));
})
.catch(console.error)
import(/* webpackChunkName: "sayac" */ "./Ticker.js")
output: {
filename: isDev
? "[name].js"
: "[name]_[hash:5].js",
path: path.resolve(__dirname, "build"),
chunkFilename: "[name].chunk.js",
},
target: "web" | "webworker" | "node" | "async-node" | "node-webkit" | "electron-main" | "electron-renderer"
bail: true | false
module: {
rules: [
{
test: /\.js?$/,
enforce: "pre",
loader: "eslint-loader"
}
]
}
return {
entry: /*************/,
output: /*************/,
resolve: {
extensions: [".js", "json", ".styl"],
alias: {
Utilities: path.resolve(__dirname, "src/utilities/"),
Templates: path.resolve(__dirname, "src/templates/")
}
},
}
import Utility from "../../utilities/utility"
yerine
import Utility from "Utilities/utility"
output: {
filename: "[name]_[hash:5].js",
path: path.resolve(__dirname, "build"),
libraryTarget: "amd",
library: "myLib"
}
libraryTarget: "var" | "amd" | "umd" | "this" | "window" | "global" | "commonjs" | "commonjs2"
webpack.LoaderOptionsPlugin
new webpack.LoaderOptionsPlugin({
minimize: true,
debug: false,
options: {
context: __dirname
}
})
tree shaking
if (false) console.log("dead code")
import { aSingleFunction } from "huge-lib"
use: [
{
loader: "babel-loader",
options: {
presets: ["react", ["env", { modules: false }]],
}
}
]