ฉบับที่ 1

webpack 4: released today!!

บทความ
Sean T. Larkin
แหล่งที่มา

หลังจาก Parcel เปิดตัวพร้อมคำโปรยที่ว่า Zero Configuration หรือแปลเป็นไทยว่า ไร้การตั้งค่าก็ทำงานได้ งานนี้ Webpack ก็ไม่หวั่นแม้วันมามากเช่นกัน และแล้ว Webpack ก็ออกเวอร์ชัน 4 ที่ชูโรงเรื่อง Zero Configuration มากขึ้นเช่นกัน ไปดูกันอย่างคร่าวๆซิว่าเวอร์ชันใหม่นี้จะไฉไลมากขึ้นหรือจัญไรสุดๆกว่ากัน

Webpack 4 มีโค้ดเนมว่า Legato ที่เป็นศัพท์แสงทางดนตรีแปลว่า การทำให้เสียงต่อเนื่อง เหตุที่เลือกใช้โค้ดเนมด้านดนตรีเพราะขาใหญ่ trivago สปอนเซอร์รายใหญ่ก็เลือกใช้โค้ดเนมทางดนตรีเช่นกัน อีกทั้ง Webpack ทำให้ตลอดทั้งแอพพลิเคชันทำงานร่วมกันได้อย่างไร้รอยต่อ ไม่ว่าจะเป็น CSS, JavaScript และอื่นๆ จึงสอดคล้องกับโค้ดเนม Legato โดยแท้

Webpack รอบนี้ไม่ได้มาเล่นๆ การันตีด้วยราคาคุยที่ว่าเร็วได้มากขึ้นถึง 98% เมื่อเทียบกับเวอร์ชันเก่า นอกจากนี้ยังมาพร้อมกับ mode การตั้งค่าใหม่ที่ให้เราระบุได้ว่ากำลังใช้งานบนโหมดไหน development หรือ production เมื่อตั้งค่านี้ Webpack จะทำการปรับประสิทธิภาพผลลัพธ์ให้เหมาะสมกับโหมดนั้น ทั้งขนาดไฟล์และช่วงเวลาการ build

Webpack 4 นอกจากการใช้งานที่ง่ายขึ้น ลดการตั้งค่าที่วุ่นวายด้วยการมีค่าตั้งต้นของการทำงานไว้ให้ แต่ก็ยังอนุญาตให้เราแก้ไขการตั้งค่าได้ optimization.splitChunks เป็นหนึ่งในค่าที่ตั้งเพิ่มเติมได้ ไอ้ตัวนี้มันเกิดมาเพื่อฆ่า CommonsChunkPlugin ที่เลิกใช้แล้วนั่นเอง

ยังมีหลายสิ่งใหม่ที่เพิ่มเข้ามาใน Webpack 4 เช่น การสนับสนุนการทำงานร่วมกับ WebAssembly อยากรู้ใช่ไหม? เสพตามลิงก์ซิ!

Developer friendly APIs using ES6 Proxies

บทความ
Khaled Garbaya
แหล่งที่มา

สมมติตอนแรกเรามีก้อนข้อมูลรูปแบบหนึ่ง เราก็ใช้ข้อมูลก้อนนี้ด้วยโครงสร้างแบบนี้ตลอดทั้งแอพพลิเคชัน ต่อมาเราอยากเปลี่ยนวิธีการเข้าถึงค่าในข้อมูลนี้ใหม่จะทำอย่างไร?

JavaScript
1const user = {
2 username: 'alibaba',
3 password: 'open sesame',
4 address: 'Far far away'
5 personalInfos: {
6 firsName: 'Ali',
7 lastName: 'Baba',
8 age: '44'
9 }
10}

แต่เดิมเราเข้าถึงค่า firstName ได้จากการเรียก user.personalInfos.firstName และเราก็ใช้ท่านี้ตลอดทั้งแอพพลิเคชันของเรา เวลาถัดมาเราอยากเข้าถึงค่านี้ด้วย user.firstName แทน ถ้าเราจะไปแก้อ็อบเจ็กต์ต้นฉบับโดยตัด personalInfos ออก แล้วย้าย firstName ให้มาอยู่ด้านนอกแทน แบบนี้เห็นท่าจะยาก เพราะส่วนอื่นของโค้ดเราอาจยังต้องการการเข้าถึงผ่าน user.personalInfos.firstName อยู่ก็เป็นได้

บทความนี้ผู้เขียนได้แก้ปัญหาด้วยการใช้เทคนิคของ Proxy ไอ้เจ้าพร็อกซี่นี้ถ้าแปลเป็นไทยก็คือตัวคั่นกลางระหว่างเรา ก่อนเคยมีเราสอง ตอนนี้มีคนที่สามยืนหัวโด่อยู่ตรงกลาง เมื่อตรงกลางมีคนอยู่ คนกลางนี้จึงเป็นได้หลายหน้าที่ เช่น หน้าที่ในการแปลงข้อความจากคนแรกไปเป็นอีกรูปแบบหนึ่งให้คนที่สองทราบ และเราจะใช้คุณสมบัตินี้ของพร็อกซี่ในการแปลงข้อมูลของเรา

JavaScript
1function proxyUser (user) {
2 const handler = {
3 get: (target, prop) => {
4 // ถ้า property ที่ต้องการเข้าถึง เช่น firstName อยู่ใต้ personalInfos
5 // ก็ดึงค่านั้นออกมา
6 if (prop in target.personalInfos) {
7 return target.personalInfos[prop]
8 }
9 // ถ้าไม่ใช่ ก็ไปควานหาจากชั้นนอกคือตัว user เอง
10 return target[prop]
11 },
12 }
13 const proxy = new Proxy(user, handler)
14 return proxy
15}

เราสามารถทำการเรียกใช้พร็อกซี่ตัวนี้ได้ด้วยการโยน user ลงไปตรงๆเลย แค่นี้เราก็เข้าถึง firstName ได้ทันทีโดยไม่ต้องผ่าน personalInfos

JavaScript
1const proxiedUser = proxyUser(user)
2proxiedUser.firstName // Ali

Efficiently snapshotting your single-page-apps with Puppeteer

บทความ
Chang Wang
แหล่งที่มา

React, Vue หรือ Angular นั้นใช้งานอย่างง่าย แต่ตอนทำเสร็จแล้วต้องเอาลิงก์ไปโพสต์ลงเฟสบุคนี่อีกเรื่อง หากทำ SSR (Server-Side Rendering) ก็ไม่ใช่ปัญหา แต่ถ้าไม่หละ? รูปตัวอย่างก็จะไม่โชว์นั่นเอง

บทความนี้ผู้เขียนสร้างเพจเพื่อเปรียบเทียบเทรนด์การดาวโหลดแพคเกจจาก NPM แล้วแสดงข้อมูลเป็นกราฟ เฮียแกอยากเอากราฟนี้ไปสร้างเป็นรูป preview เวลาโพสต์ลงเฟสบุค จึงได้แก้ปัญหานี้ด้วยการใช้ puppeteer เพื่อเรียกเบราเซอร์ Chrome รุ่นเบาหวิวมาทำการอ่านหน้าเพจ แล้วทำการสร้าง Screen Shot ขึ้นมา

ตอนแรกเฮียแกก็แก้ปัญหาแบบลูกทุ่งด้วยการปลุก Headless Chrome ขึ้นมา เปิดหน้าเพจ แล้วทำการพิมพ์ Screen Shot จากนั้นก็ถีบหัวส่งแบบไม่ใยดีด้วยการสั่งปิดเบราเซอร์เสีย พอจะถ่ายรูปอีกครั้ง ก็เปิดเบราเซอร์ เรียกหน้าเพจ ถ่ายรูปแชะๆ วนไปเลยจ้า~ เสียเวลาน่าดู

ด้วยการบำเพ็ญเพียรอย่างยาวนาน เฮียแกจึงตรัสรู้ได้ว่า การเปิดปิดเบราเซอร์ตลอดเวลานั้นเสียเวลาทำมาหากินชิบ พี่แกเลยใช้ไลบรารี่ generic-pool เพื่อทำการสร้าง pool ของเบราเซอร์ หากใครไม่เข้าใจว่า pool คืออะไร ให้จินตนาการถึงร้านนวดแห่งหนึ่ง การมี pool คือการมีเด็กนั่งเรียงอยู่หน้าตู้ ในที่นี้ pool ก็คือตู้ที่เอาไว้ยัดหมอนวดนั่นเอง เมื่อแขกมาก็เลือกเด็กจากในตู้ไปนวด นวดเสร็จเด็กก็หลั่นล้ากลับเข้าตู้ เพื่อให้แขกคนถัดไป reuse ได้อีกครั้ง ทางกลับกันหากไม่มี pool ความหมายคือเมื่อหมอนวดเสร็จก็สะบัดตูดกลับบ้านได้เลย เพราะไม่มีตู้ให้กลับ ลูกค้าคนถัดไปจึงต้องใช้หมอนวดคนใหม่เสมอ แค่ฟังก็เปลืองแล้วใช่ไหมละ

JavaScript
1const genericPool = require('generic-pool');
2const puppeteer = require('puppeteer');
3
4const factory = {
5 create: async function() {
6 const browser = await puppeteer.launch();
7 const page = await browser.newPage();
8 await page.setViewport({ width: 800, height: 420 });
9 return page;
10 },
11 destroy: function(puppeteer) {
12 puppeteer.close();
13 },
14};
15
16const browserPagePool = genericPool.createPool(factory, {
17 max: 10,
18 min: 2,
19 maxWaitingClients: 50,
20});
21
22module.exports = async url => {
23 const page = await browserPagePool.acquire();
24 await page.goto(url, { waitUntil: 'networkidle0', timeout: 60000 });
25 const screenshot = await page.screenshot();
26 await browserPagePool.release(page);
27 return screenshot;
28};

นอกจากการใช้ pool แล้ว ผู้เขียนยังมีเทคนิคอื่นๆอีก เช่น การแคชรูป Screen Shot เอาไว้ จะได้ไม่ต้องเสียเวลาสร้างใหม่ทุกครั้ง

How to make responsiveness super simple with CSS Variables

บทความ
Per Harald Borgen
แหล่งที่มา

การพัฒนาเว็บสมัยนี้ใครเขาออกแบบให้เปิดได้แค่ Desktop กันหละ มือถือแม้จะไม่ใช่ iPhone X ที่ฟันกำไรไปกว่า 64% ก็ต้องเปิดดูได้อย่างสวยงามด้วย เหตุนี้ CSS Media Query จึงเป็นฮีโร่ของพวกเรา

CSS
1/* ขนาดสำหรับมือถือ */
2@media all and (max-width: 450px) {
3
4 navbar {
5 margin: 15px 0;
6 }
7
8 navbar a {
9 font-size: 20px;
10 }
11
12 h1 {
13 font-size: 20px;
14 }
15 .grid {
16 margin: 15px 0;
17 grid-template-columns: 200px;
18 }
19}
20
21/* ขนาดสำหรับจอใหญ่กว่ามือถือ */
22h1 {
23 font-size: 30px;
24}
25#navbar {
26 margin: 30px 0;
27}
28#navbar a {
29 font-size: 30px;
30}
31.grid {
32 margin: 30px 0;
33 grid-template-columns: 200px 200px;
34}

พบว่าโค้ดข้างบนจากทั้งสองขนาดจอ ไม่ว่าจะมือถือหรือขนาดที่ใหญ่กว่าต่างก็เป็นโค้ดชุดเดียวกัน หากต่างกันแต่ตัวเลขเท่านั้น ผู้เขียนบทความจึงแนะนำให้ใช้ CSS Variables เพื่อประกาศตัวแปรสำหรับตัวเลขที่แตกต่างของทั้งสองขนาดจอ ส่วนอื่นของโค้ดจึงมีชุดเดียวได้

CSS
1/* โค้ดที่ซ้ำซ้อนมีชุดเดียว แต่ใช้การแทนที่ค่าของตัวแปรเข้าช่วย*/
2#navbar {
3 margin: var(--base-margin) 0;
4}
5#navbar a {
6 font-size: var(--base-font-size);
7}
8h1 {
9 font-size: var(--base-font-size);
10}
11.grid {
12 margin: var(--base-margin) 0;
13 grid-template-columns: var(--columns);
14}
15
16/* ขนาดจอปกติใช้ตัวแปรเซ็ตนี้ */
17:root {
18 --base-font-size: 30px;
19 --columns: 200px 200px;
20 --base-margin: 30px;
21}
22
23/* ขนาดจอมือถือใช้เซ็ตนี้ */
24@media all and (max-width: 450px) {
25 :root {
26 --columns: 200px;
27 --base-margin: 15px;
28 --base-font-size: 20px;
29}

จบแบบสวยๆ ~

Securing Your GraphQL API from Malicious Queries

บทความ
Max Stoiber
แหล่งที่มา

แม้ GraphQL จะยกระดับความง่ายของการร้องขอข้อมูล แต่แลกมาด้วยการเปิดโอกาสให้ผู้ประสงค์ร้ายร้องขอข้อมูลจำนวนมหาศาลหรือทำการโจมตีแบบ DoS (Denial of Service) ได้ง่ายเช่นกัน

บทความนี้ผู้เขียนข้อมูล Messages เป็นลูกของ Thread ในทำนองเดียวกัน Thread ก็สามารถเป็นลูกของ Message ได้เช่นกัน ไอ้โม่งผู้ไม่หวังดีจึงก่อการร้ายด้วยการร้องขอข้อมูลมหาศาลเช่นนี้

Code
1query maliciousQuery {
2 thread(id: "some-id") {
3 messages(first: 99999) {
4 thread {
5 messages(first: 99999) {
6 thread {
7 messages(first: 99999) {
8 thread {
9 # ...ขับวนไปค่ะ...
10 }
11 }
12 }
13 }
14 }
15 }
16 }
17}

แล้วไงใครแคร์?... แม้เราจะไม่แคร์ แต่ผู้เขียนแคร์ นั่นเพราะการยิงการร้องขอนี้ด้วยปริมาณมหาศาลอาจทำให้เซิฟเวอร์ของเราถึงแก่กรรมก่อนวัยอันควร

เพื่อไม่ให้สถานการณ์เช่นนี้เกิดขึ้น ผู้เขียนจึงเสนอแนวทางป้องกันไว้หลายวิธี จนผมขี้เกียจสรุปเลยหละ ดังนี้

เมื่อการร้องขอข้อมูลจำนวนมหาศาลคือปัญหา เราก็ จำกัดขนาดของ Query ซะซิ โดยการตรวจความยาวของ Query หากเกินที่เรากำหนดก็ถีบหัวส่งออกไป แต่วิธีนี้ก็มีข้อเสียอยู่ นั่นคือเดอะแก๊งค์อาจร้องขอข้อมูลเยอะๆ เช่นเดิม แต่เลือกใช้ฟิลด์ที่มีชื่อสั้นแทนเพื่อลดความยาว Query ฉลาดจริงไอ้หอยขม!

งั้นเอาใหม่ จำกัดความยาวเป็นปัญหานักใช่ไหม งั้น จัดทำชุดของ Whitelist ซะเลย ถ้า Query ที่เข้ามาอยู่ในลิสต์นี้ก็ทำงานต่อ หากไม่ใช่ให้ say goodbye ลาก่อย สำหรับข้อนี้ผู้เขียนแนะนำให้ใช้ persistgraphql ไลบรารี่เทพที่จะแงะโค้ดของเราว่าในแอพพลิเคชันเรามี Query อะไรบ้าง แล้วจัดแจงทำ Query เหล่านั้นให้เป็น Whitelist ฉะนั้นถ้าไอ้หอยขมที่ไหนใช้ Query แปลกปลอมก็เสียใจด้วย คุณไม่ได้ไปต่อ~

วิธีข้างต้นเหมือนจะดี แต่ก็มีข้อเสียบางอย่างเช่น เราไม่สามารถทำ API ของเราให้ชาวโลกใช้ได้ ผู้เขียนจึงเสนอวิธีถัดไป เสนออะไรนักหนาขี้เกียจเรียบเรียงนะไอ้หอยแครง!

จำกัดความลึกของการค้นหาคืออีกแนวทางที่เสนอ การโจมตีโดยมากมักเน้นไปที่การร้องขอข้อมูลผ่าน Query ที่ซ้อนไปมาหลายชั้น ผู้เขียนจึงแนะนำให้ใช้ graphql-depth-limit เพื่อจำกัดชั้นของข้อมูล

สุดท้ายคือ การวิเคราะห์ความซับซ้อนของการร้องขอข้อมูล โดยการบอกว่าข้อมูลตัวไหนมีค่าความซับซ้อนอยู่ที่เท่าไหร่ จากนั้นจึงคำนวณความซับซ้อนทั้งหมดของการร้องขอข้อมูล หากการร้องขอนั้นซับซ้อนเกินไปก็แค่ไม่เหลียวแล

จบ...