Code splitting แบบใหม่ใน Webpack 4

สำหรับคนใช้ Webpack ผมเชื่อว่าหลายๆ คนจะใช้ code splitting อยู่ และ code splitting ที่ง่ายๆ โง่ๆ ตรงไปตรงมาที่สุดที่น่าจะใช้กันก็คือ vendor code split หรือการแยกโค้ดใน node_modules ไปเป็นอีก bundle เนื่องจากว่าโค้ดในนั้นมักจะอัพเดตไม่บ่อยเท่าโค้ดเรา ก็จะช่วยให้แคชได้นานขึ้น

แต่พอถึงยุค Webpack 4 แล้ว เราสามารถทำ Vendor code split ได้มากขึ้น และเผลอๆ จะ split ได้ยิบย่อยลงไปได้อีกด้วยระบบ Chunk graph

ปัญหาของ code split

ก่อนจะไปถึง vendor code split นั้นจะขอเล่าถึง Code split แบบใหม่ใน Webpack 4 ก่อน

ใน Webpack 3 นั้นหากเราทำ async code splitting นั้นเราจะได้ Bundle ประมาณนี้

จากรูปจะเห็นปัญหาว่า หาก entrypoint ไม่ได้ใช้ lodash แต่ทั้ง Overlay และ Settings chunk ใช้ lodash จะทำให้ Webpack เอา lodash ไปใส่ทั้ง 2 bundle user ก็ต้องโหลด lodash หลายรอบ

วิธีแก้ไขในอดีตก็มักจะใช้ vendor code split เพื่อแยก node_modules ออกไป (หรือ CommonsChunkPlugin ที่แยกตามจำนวนครั้งที่ใช้)

หรือถ้าโกงหน่อยก็จะใส่ require('lodash') ไว้ใน entrypoint เพื่อบังคับให้ entrypoint มีการเรียก lodash

แต่ปัญหาก็ยังไม่จบแค่นั้น เพราะพอเราแยก node_modules ออกไปก็จะได้ chunk ใหม่ที่มีทุกมอดูลรวมกันอยู่ ก็กลายเป็นว่าพอเข้าหน้า Overlay ก็ต้องโหลด jQuery มาด้วยถึงจะไม่ได้ใช้ (ในรูปไม่ได้วาดไว้ แต่สมมุติว่ามีหน้าที่ใช้ jQuery หลายหน้า)

ในเว็บ TipMe ก็มีปัญหาแบบนี้เช่นเดียวกันเพราะเว็บเราใช้ jQuery แต่ก็จะมีบาง widget ที่ใช้ React เขียน ก็คงไม่เหมาะที่จะต้องโหลด React ทุกหน้า วิธีที่เราทำอยู่คือทำ DLL bundle 3 ชุด

  • jQuery
  • React
  • Runtime (common ระหว่าง 2 bundle ด้านบน เช่น Polyfill)

เสร็จแล้วเวลา build เราจะมี Gulp filter ที่อ่านไฟล์แล้วดูว่าไฟล์นี้มีคำสั่ง // use React หรือเปล่า ถ้ามีก็จะ link กับ jQuery + React DLL แต่ถ้าไม่มีก็จะ link กับ jQuery อย่างเดียว

วิธีนี้ก็ยังไม่ใช่วิธีที่ดีเท่าไร เพราะ

  • DLL ก็ยังมีมอดูลที่ไม่จำเป็นอยู่
  • ต้องนั่ง list module ที่ใช้ DLL เอง ซึ่งไม่รู้ว่าเราเลือกมาแล้วดีที่สุดหรือเปล่า
  • DLL ทำ Tree shaking กับโค้ดเราไม่ได้ เพราะเวลา build DLL มันไม่อ่านโค้ดเรา
  • Build tooling ซับซ้อน
  • เวลา build ก็ซับซ้อน ต้องรัน Gulp 3 รอบ คือ build runtime, build DLL แล้วถึงจะ build app
  • เวลาใช้ก็ซับซ้อน เพราะที่หน้าต้อง include DLL ให้ถูกว่าหน้านี้ใช้อะไร

All new code splitting

Webpack 4 นั้นจะเปลี่ยนวิธี split ใหม่โดยค่าเริ่มต้นนั้นจะตัด vendor chunk ได้ฉลาดมากขึ้น

นั่นคือ Webpack สามารถสร้าง chunk ที่เป็น common ระหว่างมอดูลได้แล้ว โดยเป็นค่า default เราไม่ต้องทำอะไรเพียงแค่ใช้ Webpack 4 เท่านั้น

แต่จะเห็นว่าชื่อไฟล์นั้นจะเป็น vendor~page-a~page-b ซึ่งพอเราเพิ่มหน้าเข้าไปไฟล์ก็จะเปลี่ยนชื่อ ถ้าต้องการ cache ให้ได้นานๆ นั้นแนะนำให้ตั้งค่า Webpack ดังนี้

{
    optimization: {
        splitChunks: {
            name: false,
        }
    }
}

ซึ่งจะทำให้ chunk ที่ได้นั้นใช้ชื่อเป็น chunk ID แทนที่จะระบุชื่อมอดูลภายใน

Entrypoint splitting

ถึงตรงนี้แล้ว splitting แบบใหม่ก็ยังไม่ดีเท่าของเก่า เพราะมันยังไม่แยก node_modules ออกจาก Entrypoint (หรือถ้ามีหลาย entrypoint มันก็ยังไม่แยก common ออกมา) เพราะค่าเริ่มต้นของ Webpack จะปิดเอาไว้

เหตุผลที่ปิดก็เดาได้ไม่ยากครับ ปกติถ้าเรา split มือ เราจะรู้ชื่อ chunk แล้วเราก็จะเขียน script tag ไปเรียกมา แต่ถ้า Webpack ทำให้ user ก็อาจจะไม่เข้าใจว่าต้องเรียก chunk ที่แยกออกมา ก็เลยปิดไว้เป็นหลัก

ทีนี้เราจะเปิดได้อย่างไร? วิธีการเปิดคือตั้งค่าดังนี้ใน Webpack

{
    optimization: {
        splitChunks: {
            chunks: 'all',
            maxInitialRequests: 5, // default to 2, adjust as needed
        },
        runtimeChunk: 'single', // optional, recommended
    }
}

ซึ่งค่าต่างๆ จะมีผลดังนี้

  • maxInitialRequests จะกำหนดว่าให้ split ได้สูงสุดกี่ chunk
  • runtimeChunk จะแยกส่วน manifest ไปเป็นอีก chunk ซึ่ง manifest จะเปลี่ยนทุกครั้งที่มีการแก้โค้ดไม่ว่าไฟล์ไหนอยู่แล้ว การแยกไปเลยก็จะทำให้ cache ได้ดีขึ้น
  • อื่นๆ สามารถดูได้ตาม docs

จากนั้นเราจะต้องแก้หน้าเว็บให้ใส่ script tag ให้ครบ แต่… ในเมื่อ Webpack generate ชื่อไฟล์มาให้ แล้วเราจะใส่ src ยังไง?

คำตอบคือในไฟล์ Webpack stats จะมีบอกอยู่ว่า Entrypoint นี้ต้องใช้ไฟล์อะไรบ้าง หน้าตาประมาณนี้

{
  "entrypoints": {
    "bootstrap": {
      "chunks": [
        28,
        10,
        11,
        49
      ],
      "assets": [
        "js/runtime~bootstrap.js",
        "js/runtime~bootstrap.js.map",
        "js/c_10.43ba1082.js",
        "js/10.js.map",
        "js/c_11.45f9671c.js",
        "js/11.js.map",
        "js/c_bootstrap.5f882904.js",
        "js/bootstrap.js.map"
      ]
    },
  }
}

(ถ้าทำ CSS Split ด้วย ก็จะได้ชื่อไฟล์ CSS มาใน assets ด้วยเช่นกัน)

สิ่งที่เราต้องทำคือ อ่านไฟล์ stats แล้วเอามาสร้าง HTML

ก่อนอื่นก็แก้ Webpack ให้สร้าง stats มาเสียก่อน โดยใช้ webpack-stats-plugin

{
    plugins: [
        new stats.StatsWriterPlugin({
            filename: 'stats.json',
            fields: ['entrypoints'],
        }),
    ],
}

จากนั้นเวลา build เราจะได้ไฟล์ stats.json ออกมา จากนั้นเราก็จะแก้ให้แอพเราอ่านไฟล์นี้ขึ้นมา

สำหรับ Django นั้นวิธีที่ผมใช้อยู่ก็คือสร้าง Template tag ขึ้นมา

โดยนำไฟล์ webpack.py ไว้ใน folder templatetags และไฟล์ webpack_entrypoint.html ไว้ใน templates จากนั้นเวลาใช้ก็

{% load webpack %}
{% entrypoint "bootstrap" %}

ก็จะได้ script tag ที่ครบถ้วนทันที

<script src="https://static.tipme.in.th/js/runtime~bootstrap.24e2bfeb3c73.js" crossorigin="anonymous"></script>
<script src="https://static.tipme.in.th/js/c_10.43ba1082.2b93f172c643.js" crossorigin="anonymous"></script>
<script src="https://static.tipme.in.th/js/c_11.45f9671c.d9617c7f9295.js" crossorigin="anonymous"></script>
<script src="https://static.tipme.in.th/js/c_bootstrap.5f882904.9c15205b28f3.js" crossorigin="anonymous"></script>

สรุป

Code split แบบใหม่นี้ลดความซับซ้อนไปในหลายๆ อย่างเลยครับ ในขณะที่เพิ่ม performance ให้ดีขึ้น และยิ่งถ้าเปิดให้มัน split entrypoint ได้ก็ยิ่งเพิ่มประสิทธิภาพอีก

Commit ที่ TipMe ปรับมาใช้ Webpack 4 นั้น สถิติคือ 144 additions, 324 deletions ลบ tooling ออกไปได้เยอะมาก

  • ไม่ต้อง mark แล้วว่า entrypoint นี้ใช้ React เพราะ Webpack จะรู้เองแล้วเอา React chunk ใส่มาใน list ให้อัตโนมัติ
  • ไม่ต้องมี webpack config หลายชุดแล้ว build ทีเดียวออกเลย
  • Build target เหลืออันเดียว
  • ใช้ mode: production แทนการเซตเอง

และสุดท้าย Webpack build time จาก 62550ms เหลือ 35162ms หรือลดลง 43%

(แต่ Gulp เราลดมาแค่ 20 วินาที เพราะมีไฟล์ที่ต้องอัพโหลดขึ้น Sentry มากขึ้น)

โอชิเมม Keyakizaka46: 2nd Anniversary Edition

จริงๆ ไม่ค่อยมีอะไรเปลี่ยน แต่ก็เปลี่ยนเยอะ เพื่อความง่ายและขี้เกียจอาจจะละคนที่ไม่ได้มีอะไรเปลี่ยนแล้วกัน

คามิโอชิ

โคบายาชิ ยุย (ยุยปง)

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

โอชิเฮน

หลังๆ มาสัญญาณโอชิเฮนริสะเราบ่อยมาก ก็พยายามคิดว่าไม่ต้องหรอก แต่คิดว่าจุด trip คือตอนที่นกบัตรจับมือ

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

คิดว่าถึงเวลาแล้วล่ะที่เราจะปล่อยให้ริสะไปเป็นนางแบบแล้วเราไปชอบยุยปงดีกว่า

ว่าที่คามิโอชิ

พอคามิเหลือ 1 คน ก็กำลังคิดว่าจะสรรหาคามิใหม่อยู่

โอเซกิ ริกะ

ไปจับมือโอเซกิมา ก็รู้สึกเฉยๆ กับน้องมากขึ้น แต่ก็ตามคอนเซปต์อ่ะคือเด็กสาวข้างบ้าน (เราว่าวงนี้เด็กสาวข้างบ้านซะยิ่งกว่า BNK48 อีกนะ)

ที่เริ่มรู้สึกคือเหมือนริสะส่งต่อให้โอเซกิ บางทีเหมือนเห็นโอเซกิเป็นริสะ v2 (จะเห็นได้ชัดตอนที่งานบรรลุนิติภาวะ ที่โอเซกิแต่งตัวมาเหมือนที่ริสะถ่ายแบบไม่กี่สัปดาห์ก่อนหน้านั้นเลย)

นากาฮามา เนรุ

ไปจับมือเนรุมาเหมือนกันแล้วก็ผิดหวังนิดหน่อยว่าน้องคุยอังกฤษกับเราไม่ได้ แต่น้องน่ารักจริง ยิ่งท่าจับหัวนี่ดาเมจมาก

อิมาอิสึมิ ยุย

ตอนไปจับมือเนรุเห็นเลนซือมินแล้วยอมรับว่าโดนตก น้องเทคแคร์คนมาจับมือมากๆ รักเลย

ปล. ตั้งแต่เห็นมิโฮะหน้าสด (ก่อนเข้าวงการ) มิโฮะก็หายไปจากลิสต์แล้ว

โอชิ

วาตานาเบะ ริสะ

ยังไม่รู้ว่าหลังเฮนแล้วจะจัดไว้ตรงไหนดี ไว้ตรงนี้ก่อนแล้วกัน

นากาซาวา นานาโกะ

นาโกะบางเวลาก็รู้สึกว่าน่าจัดขึ้นไปรอคามิ แต่ก็ยังแค่บางเวลาอยู่ดี

สุซุโมโต้ มิยู

หลังๆ มามงก็น่ารักดีนะ

รอเลื่อนขั้น

โคอิเคะ มินามิ

Bathroom Travel น่ารักดี ติดไว้ก่อนนะ

เฉยๆ

ฮิราเทะ ยูรินะ

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

มีความรู้สึกว่าตอนนี้เทะเหมือนเป็นนอมินี เพราะเทะเองก็ไม่ฟิตสำหรับซิงเกิลนี้อยู่แล้ว ถ้าเป็นคนอื่นอาจจะได้ย้ายออกไปเหมือนที่ซือมินไปตกแถวหลังใน Kazenifukaretemo แต่ด้วยภาพจำของวงที่เป็นแบบนี้ก็เลยให้น้องเป็นเซนเตอร์บนกระดาษต่อไป ในขณะที่งานจริงๆ ก็ใช้คนอื่นเซนเตอร์

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

ไม่รู้ว่าจะมีการวนเซนเตอร์ระหว่างนี้หรือเปล่า อาจจะเป็นโอกาสดีที่จะได้เทสความเป็นเซนเตอร์แต่ละคน แต่ในขณะเดียวกันก็รู้สึกว่านอกจากเทะแล้วไม่มีใครที่ทำภาพเกรี้ยวกราดได้แล้วนะ (Za cool? อาจจะได้แต่ฝีมือคงไม่ถึงเซนเตอร์)

อุเอมุระ รินะ

อุเอมุระเริ่มโตเกินไปแล้วอ่ะ ถึงจะยังหน้าดูเด็กอยู่แต่ก็เริ่มไม่ค่อยละ

วาตานาเบะ ริกะ

เริ่มรู้สึกว่านี่ก็สองปีแล้ว ริกะควรจะหลุดจากคาแรคเตอร์กลัวกล้องได้แล้วนะ มันเริ่มรู้สึกว่า fake แล้วแทนที่จะดีขึ้นกลับแย่ลง แต่ก่อนเรายังจะเห็นริกะมี “กล้องน่ากลัว” “Revolution” เดี๋ยวนี้หายไปเลยได้แค่เย่คำเดียว

ถึงจะพูดแบบนั้นแต่ก็คิดว่า ถ้ามีโอกาสไปจับมือครั้งหน้ายังอยากไปลองจับมือริกะอยู่นะ

โยเนทานิ นานามิ

ชอบนิสัย ถ้าโยเนะน่ารักกว่านี้อาจจะขึ้นไปได้

อื่นๆ

  • โมนะ
  • เบริกะ
  • ฮาบุ
  • สุไก

Not interested

ฮาราดะ อาโอย

เริ่มมาได้ยินว่าฮาราดะกำลัง transform ไปในคาแรคเตอร์สาวอยู่บ้าง ในรายการน่าสงสารมากคือไม่มีบทมาหลายเดือน เหมือนมาแทบจะนั่งหลับอยู่แล้ว

แต่ก็ยังมองไม่เห็นนะว่าฮาราดะจะกลับมาอย่างไร มันไม่ใช่แค่ evolution แต่มันต้องถึงขนาด reboot แล้วล่ะ

อื่นๆ

  • อิชิโมริ
  • โอดะ
  • ซาโต้
  • ไซโต้
  • โมริยะ