แอปพลิเคชันฟรอนต์เอนด์หลายล้านตัวกำลังจัดการการสร้างเฉพาะสภาพแวดล้อม สำหรับแต่ละสภาพแวดล้อม ไม่ว่าจะเป็นการพัฒนา การจัดเตรียม หรือการผลิต จะต้องมีการสร้างแอปฟรอนต์เอนด์แยกกัน และต้องตั้งค่าตัวแปรสภาพแวดล้อมที่ถูกต้อง จำนวนการสร้างจะเพิ่มขึ้นหากมีแอปหลายตัวเข้ามาเกี่ยวข้อง ทำให้ยิ่งทำให้หงุดหงิด นี่เป็นปัญหาทั่วไปมาเป็นเวลานานแล้ว แต่มีวิธีที่ดีกว่าในการจัดการตัวแปรสภาพแวดล้อม ฉันพบวิธีที่จะปรับกระบวนการนี้ให้มีประสิทธิภาพ และในบทความนี้ ฉันจะแนะนำคุณทีละขั้นตอนเพื่อสร้างกระบวนการที่มีประสิทธิภาพซึ่งจะช่วยลดเวลาในการสร้างและช่วยให้คุณมั่นใจได้ถึงความสม่ำเสมอในสภาพแวดล้อมต่างๆ ในโปรเจ็กต์ของคุณ การทำความเข้าใจตัวแปรสภาพแวดล้อม ก่อนจะเริ่มต้น ฉันคิดว่าเราควรทบทวนกันก่อน แอปพลิเคชันเว็บมักจะพึ่งพาตัวแปรที่เรียกว่า " ซึ่งมักรวมถึงจุดสิ้นสุดของระบบภายใน ระบบบูรณาการ คีย์ระบบการชำระเงิน หมายเลขการเผยแพร่ และอื่นๆ โดยธรรมชาติแล้ว ค่าของตัวแปรเหล่านี้จะแตกต่างกันไปขึ้นอยู่กับสภาพแวดล้อมที่แอปพลิเคชันถูกปรับใช้ "ตัวแปรสภาพแวดล้อม ตัวอย่างเช่น ลองนึกภาพแอปพลิเคชันที่โต้ตอบกับเกตเวย์การชำระเงิน ในสภาพแวดล้อมการพัฒนา URL ของเกตเวย์การชำระเงินอาจชี้ไปที่แซนด์บ็อกซ์เพื่อการทดสอบ (https://sandbox.paymentgateway.com) ในขณะที่ในสภาพแวดล้อมการผลิต URL นั้นจะชี้ไปที่บริการสด (https://live.paymentgateway.com) ในทำนองเดียวกัน คีย์ API ที่แตกต่างกันหรือการตั้งค่าเฉพาะสภาพแวดล้อมอื่นๆ จะใช้สำหรับสภาพแวดล้อมแต่ละแห่งเพื่อให้แน่ใจว่าข้อมูลมีความปลอดภัยและหลีกเลี่ยงการสับสนระหว่างสภาพแวดล้อม ความท้าทายในการพัฒนาฝั่งฟรอนต์เอนด์ เมื่อสร้าง นี่ไม่ใช่ปัญหา การประกาศตัวแปรเหล่านี้ในโค้ดแอปพลิเคชันก็เพียงพอแล้ว เนื่องจากค่าของตัวแปรเหล่านี้ถูกเก็บไว้ในสภาพแวดล้อมเซิร์ฟเวอร์ที่ปรับใช้แบ็กเอนด์ วิธีนี้ทำให้แอปพลิเคชันแบ็กเอนด์เข้าถึงตัวแปรเหล่านี้ได้เมื่อเริ่มต้นระบบ แอปพลิเคชันแบ็กเอนด์ อย่างไรก็ตาม มีความซับซ้อนมากขึ้นเล็กน้อย เนื่องจากแอปพลิเคชันเหล่านี้ทำงานในเบราว์เซอร์ของผู้ใช้ จึงไม่สามารถเข้าถึงค่าตัวแปรสภาพแวดล้อมที่เฉพาะเจาะจงได้ เพื่อแก้ไขปัญหานี้ ค่าของตัวแปรเหล่านี้มักจะถูก "ฝัง" ไว้ในแอปพลิเคชันแบบ frontend ในเวลาที่สร้าง วิธีนี้ เมื่อแอปพลิเคชันทำงานในเบราว์เซอร์ของผู้ใช้ ค่าที่จำเป็นทั้งหมดจะถูกฝังไว้ในแอปพลิเคชันแบบ frontend เรียบร้อยแล้ว แอปพลิเคชันแบบ frontend แนวทางนี้ เช่นเดียวกับแนวทางอื่นๆ อีกมากมาย มาพร้อมคำเตือน: คุณจะต้องสร้างแอปพลิเคชันส่วนหน้าแบบแยกกันสำหรับแต่ละสภาพแวดล้อม เพื่อให้แต่ละแอปพลิเคชันจะมีค่าที่เกี่ยวข้องของตัวเอง สมมติว่าเรามีสามสภาพแวดล้อม: ตัวอย่างเช่น การพัฒนาเพื่อการทดสอบภายใน ขั้นตอนการทดสอบบูรณาการ และการผลิตเพื่อลูกค้า หากต้องการส่งงานของคุณเพื่อทดสอบ คุณต้องสร้างแอปและปรับใช้ในสภาพแวดล้อมการพัฒนา หลังจากการทดสอบภายในเสร็จสิ้น คุณต้องสร้างแอปอีกครั้งเพื่อปรับใช้ในขั้นตอนแรก จากนั้นจึงสร้างอีกครั้งเพื่อปรับใช้ในการผลิต หากโครงการมีแอปพลิเคชันฟรอนต์เอนด์มากกว่าหนึ่งรายการ จำนวนการสร้างดังกล่าวจะเพิ่มขึ้นอย่างมาก นอกจากนี้ ระหว่างการสร้างเหล่านี้ ฐานโค้ดจะไม่เปลี่ยนแปลง โดยการสร้างครั้งที่สองและครั้งที่สามจะอิงตามโค้ดต้นฉบับเดียวกัน ทั้งหมดนี้ทำให้กระบวนการเผยแพร่ยุ่งยาก ช้า และมีค่าใช้จ่ายสูง รวมถึงมีความเสี่ยงต่อการรับประกันคุณภาพ อาจเป็นไปได้ว่ารุ่นนั้นได้รับการทดสอบอย่างดีในสภาพแวดล้อมการพัฒนา แต่รุ่นที่สร้างขึ้นในขั้นตอนนี้ถือเป็นรุ่นใหม่ในทางเทคนิค ซึ่งหมายความว่ามีความเสี่ยงต่อข้อผิดพลาดที่อาจเกิดขึ้นได้ คุณมีแอปพลิเคชันสองตัวที่มีเวลาในการสร้าง X และ Y วินาที สำหรับสภาพแวดล้อมทั้งสามนี้ แอปพลิเคชันทั้งสองจะใช้เวลาในการสร้าง อย่างไรก็ตาม หากคุณสามารถสร้างแอปพลิเคชันแต่ละตัวเพียงครั้งเดียวและใช้การสร้างเดียวกันในทุกสภาพแวดล้อม เวลาทั้งหมดจะลดลงเหลือเพียง วินาที ซึ่งช่วยลดเวลาในการสร้างได้สามเท่า ตัวอย่าง: 3X + 3Y X + Y ปัญหานี้สร้างความแตกต่างอย่างมากให้กับระบบ frontend pipeline ซึ่งทรัพยากรมีจำกัด และเวลาในการสร้างอาจใช้เวลาเพียงไม่กี่นาทีไปจนถึงชั่วโมงกว่าๆ ปัญหาดังกล่าวพบได้ในแอปพลิเคชัน frontend เกือบทุกแอปพลิเคชันทั่วโลก และมักไม่มีวิธีแก้ไข อย่างไรก็ตาม ปัญหานี้ถือเป็นปัญหาร้ายแรง โดยเฉพาะในมุมมองทางธุรกิจ จะดีแค่ไหนหากแทนที่จะสร้างบิลด์แยกกันสามบิลด์ คุณสามารถสร้างบิลด์เดียวแล้วนำไปใช้งานในทุกสภาพแวดล้อมได้ ฉันพบวิธีที่จะทำแบบนั้นได้ การเพิ่มประสิทธิภาพการใช้งานส่วนหน้า คู่มือ การตั้งค่าตัวแปรสภาพแวดล้อม ขั้นแรก คุณต้องสร้างไฟล์ในที่เก็บของโปรเจ็กต์ฟรอนต์เอนด์ของคุณ ซึ่งตัวแปรสภาพแวดล้อมที่จำเป็นจะถูกแสดงรายการไว้ ตัวแปรเหล่านี้จะถูกใช้โดยนักพัฒนาในเครื่อง โดยทั่วไป ไฟล์นี้จะเรียกว่า ซึ่งเฟรมเวิร์กฟรอนต์เอนด์ที่ทันสมัยส่วนใหญ่สามารถอ่านได้ นี่คือตัวอย่างของไฟล์ดังกล่าว: .env.local CLIENT_ID='frontend-development' API_URL=/api/v1' PUBLIC_URL='/' COMMIT_SHA='' หมายเหตุ: เฟรมเวิร์กที่แตกต่างกันต้องการรูปแบบการตั้งชื่อที่แตกต่างกันสำหรับตัวแปรสภาพแวดล้อม ตัวอย่างเช่น ใน React คุณต้องเพิ่ม ไว้ข้างหน้าชื่อตัวแปร ไฟล์นี้ไม่จำเป็นต้องรวมตัวแปรที่ส่งผลต่อแอปพลิเคชันโดยตรง แต่สามารถมีข้อมูลการดีบักที่เป็นประโยชน์ได้ด้วย ฉันได้เพิ่มตัวแปร ซึ่งเราจะดึงมาจากงานสร้างในภายหลังเพื่อติดตามการคอมมิตที่บิลด์นี้ใช้ REACT_APP_ COMMIT_SHA ขั้นตอนต่อไป ให้สร้างไฟล์ชื่อ ซึ่งคุณสามารถกำหนดตัวแปรสภาพแวดล้อมที่คุณต้องการได้ เฟรมเวิร์กส่วนหน้าจะแทรกตัวแปรเหล่านี้ให้คุณ ตัวอย่างเช่น สำหรับ React ตัวแปรเหล่านี้จะถูกเก็บไว้ในอ็อบเจ็กต์ : environment.js process.env const ORIGIN_ENVIRONMENTS = window.ORIGIN_ENVIRONMENTS = { CLIENT_ID: process.env.CLIENT_ID, API_URL: process.env.API_URL, PUBLIC_URL: process.env.PUBLIC_URL, COMMIT_SHA: process.env.COMMIT_SHA }; export const ENVIRONMENT = { clientId: ORIGIN_ENVIRONMENTS.CLIENT_ID, apiUrl: ORIGIN_ENVIRONMENTS.API_URL, publicUrl: ORIGIN_ENVIRONMENTS.PUBLIC_URL ?? "/", commitSha: ORIGIN_ENVIRONMENTS.COMMIT_SHA, }; ที่นี่ คุณจะเรียกค่าเริ่มต้นทั้งหมดสำหรับตัวแปรใน ซึ่งช่วยให้คุณดูค่าเหล่านี้ในคอนโซลของเบราว์เซอร์ได้ นอกจากนี้ คุณยังต้องคัดลอกค่าเหล่านี้ลงในอ็อบเจ็กต์ ซึ่งคุณสามารถตั้งค่าเริ่มต้นบางอย่างได้ด้วย ตัวอย่างเช่น เราถือว่า เป็น / ตามค่าเริ่มต้น ใช้ object ทุกที่ที่จำเป็นต้องใช้ตัวแปรเหล่านี้ในแอปพลิเคชัน window.ORIGIN_ENVIRONMENTS ENVIRONMENT publicUrl ENVIRONMENT ในขั้นตอนนี้ คุณได้ตอบสนองความต้องการทั้งหมดสำหรับการพัฒนาในท้องถิ่นแล้ว แต่เป้าหมายคือการจัดการกับสภาพแวดล้อมที่แตกต่างกัน ในการดำเนินการนี้ ให้สร้างไฟล์ โดยมีเนื้อหาต่อไปนี้: .env CLIENT_ID='<client_id>' API_URL='<api_url>' PUBLIC_URL='<public_url>' COMMIT_SHA=$COMMIT_SHA ในไฟล์นี้ คุณจะต้องระบุตัวแทนสำหรับตัวแปรที่ขึ้นอยู่กับสภาพแวดล้อม ตัวแทนสามารถเป็นอะไรก็ได้ที่คุณต้องการ ตราบใดที่ไม่ซ้ำกันและไม่ทับซ้อนกับโค้ดต้นฉบับของคุณในทางใดทางหนึ่ง สำหรับความมั่นใจเพิ่มเติม คุณยังสามารถใช้ UUID คืออะไร – ตัวระบุเฉพาะสากล สำหรับตัวแปรที่ไม่เปลี่ยนแปลงระหว่างสภาพแวดล้อม (เช่น แฮชคอมมิต) คุณสามารถเขียนค่าจริงได้โดยตรงหรือใช้ค่าที่พร้อมใช้งานระหว่างงานสร้าง (เช่น ) เฟรมเวิร์กส่วนหน้าจะแทนที่ตัวแทนเหล่านี้ด้วยค่าจริงระหว่างกระบวนการสร้าง: $COMMIT_SHA ไฟล์ ตอนนี้คุณมีโอกาสที่จะใส่ค่าจริงแทนค่าตัวแทนได้ เมื่อต้องการทำเช่นนี้ ให้สร้างไฟล์ (ฉันเลือก Python แต่คุณสามารถใช้เครื่องมืออื่นเพื่อจุดประสงค์นี้) ซึ่งควรมีการแมปค่าตัวแทนกับชื่อตัวแปรก่อน: inject.py replacement_map = { "<client_id>": "CLIENT_ID", "<api_url>": "API_URL", "<public_url>": "PUBLIC_URL", "%3Cpublic_url%3E": "PUBLIC_URL" } โปรดทราบว่า จะแสดงสองครั้ง และรายการที่สองมีวงเล็บ escape คุณต้องใช้สิ่งนี้สำหรับตัวแปรทั้งหมดที่ใช้ในไฟล์ CSS และ HTML public_url ตอนนี้เรามาเพิ่มรายการไฟล์ที่เราต้องการแก้ไขกัน (นี่จะเป็นตัวอย่างของ Nginx): base_path = 'usr/share/nginx/html' target_files = [ f'{base_path}/static/js/main.*.js', f'{base_path}/static/js/chunk.*.js', f'{base_path}/static/css/main.*.css', f'{base_path}/static/css/chunk.*.css', f'{base_path}/index.html' ] จากนั้น เราสร้างไฟล์ ซึ่งเราจะได้รับไฟล์การแมปและรายการของไฟล์อาร์ทิแฟกต์สำหรับการสร้าง (เช่น ไฟล์ JS, HTML และ CSS) และแทนที่ตัวแทนด้วยค่าของตัวแปรจากสภาพแวดล้อมปัจจุบันของเรา: injector.py import os import glob def inject_envs(filename, replacement_map): with open(filename) as r: lines = r.read() for key, value in replacement_map.items(): lines = lines.replace(key, os.environ.get(value) or '') with open(filename, "w") as w: w.write(lines) def inject(target_files, replacement_map, base_path): for target_file in target_files: for filename in glob.glob(target_file.glob): inject_envs(filename, replacement_map) จากนั้นในไฟล์ ให้เพิ่มบรรทัดนี้ (อย่าลืมนำเข้า ): inject.py injector.py injector.inject(target_files, replacement_map, base_path) ตอนนี้เราต้องตรวจสอบให้แน่ใจว่าสคริปต์ จะทำงานเฉพาะในระหว่างการปรับใช้เท่านั้น คุณสามารถเพิ่มสคริปต์นี้ลงใน โดยใช้คำสั่ง ได้หลังจากติดตั้ง Python และคัดลอกสิ่งประดิษฐ์ทั้งหมด: inject.py Dockerfile CMD RUN apk add python3 COPY nginx/default.conf /etc/nginx/conf.d/default.conf COPY --from=build /app/ci /ci COPY --from=build /app/build /usr/share/nginx/html CMD ["/bin/sh", "-c", "python3 ./ci/inject.py && nginx -g 'daemon off;'"]That's it! This way, during each deployment, the pre-built files will be used, with variables specific to the deployment environment injected into them. เท่านี้เอง! ด้วยวิธีนี้ ในระหว่างการปรับใช้แต่ละครั้ง จะมีการใช้ไฟล์ที่สร้างไว้ล่วงหน้า โดยมีตัวแปรที่เฉพาะเจาะจงกับสภาพแวดล้อมการปรับใช้ที่แทรกเข้าไป ไฟล์: การจัดการการแฮชชื่อไฟล์เพื่อการแคชเบราว์เซอร์ที่เหมาะสม สิ่งหนึ่งคือ หากอาร์ทิแฟกต์ที่คุณสร้างมีแฮชเนื้อหาในชื่อไฟล์ การแทรกนี้จะไม่ส่งผลต่อชื่อไฟล์ และอาจทำให้เกิดปัญหาในการแคชเบราว์เซอร์ได้ ในการแก้ไขปัญหานี้ หลังจากแก้ไขไฟล์ด้วยตัวแปรที่แทรกแล้ว คุณจะต้อง: สร้างแฮชใหม่สำหรับไฟล์ที่อัปเดต ผนวกแฮชใหม่นี้เข้ากับชื่อไฟล์เพื่อให้เบราว์เซอร์ปฏิบัติต่อไฟล์เหล่านี้เหมือนเป็นไฟล์ใหม่ อัปเดตการอ้างอิงถึงชื่อไฟล์เก่าในโค้ดของคุณ (เช่น คำสั่งนำเข้า) ให้ตรงกับชื่อไฟล์ใหม่ ในการใช้งานนี้ ให้เพิ่มไลบรารีแฮช import ( ) และฟังก์ชันต่อไปนี้ลงในไฟล์ import hashlib inject.py def sha256sum(filename): h = hashlib.sha256() b = bytearray(128 * 1024) mv = memoryview(b) with open(filename, 'rb', buffering=0) as f: while n := f.readinto(mv): h.update(mv[:n]) return h.hexdigest() def replace_filename_imports(filename, new_filename, base_path): allowed_extensions = ('.html', '.js', '.css') for path, dirc, files in os.walk(base_path): for name in files: current_filename = os.path.join(path, name) if current_filename.endswith(allowed_extensions): with open(current_filename) as f: s = f.read() s = s.replace(filename, new_filename) with open(current_filename, "w") as f: f.write(s) def rename_file(fullfilename): dirname = os.path.dirname(fullfilename) filename, ext = os.path.splitext(os.path.basename(fullfilename)) digest = sha256sum(fullfilename) new_filename = f'{filename}.{digest[:8]}' new_fullfilename = f'{dirname}/{new_filename}{ext}' os.rename(fullfilename, new_fullfilename) return filename, new_filename อย่างไรก็ตาม ไม่จำเป็นต้องเปลี่ยนชื่อไฟล์ทั้งหมด ตัวอย่างเช่น ชื่อไฟล์ จะต้องไม่เปลี่ยนแปลง และเพื่อให้บรรลุเป้าหมายนี้ ให้สร้างคลาส ที่จะจัดเก็บแฟล็กที่ระบุว่าจำเป็นต้องเปลี่ยนชื่อหรือไม่: index.html TargetFile class TargetFile: def __init__(self, glob, should_be_renamed = True): self.glob = glob self.should_be_renamed = should_be_renamed ตอนนี้คุณเพียงแค่แทนที่อาร์เรย์ของเส้นทางไฟล์ใน ด้วยอาร์เรย์ของอ็อบเจ็กต์คลาส : inject.py TargetFile target_files = [ injector.TargetFile(f'{base_path}/static/js/main.*.js'), injector.TargetFile(f'{base_path}/static/js/chunk.*.js'), injector.TargetFile(f'{base_path}/static/css/main.*.css'), injector.TargetFile(f'{base_path}/static/css/chunk.*.css'), injector.TargetFile(f'{base_path}/index.html', False) ] และอัปเดตฟังก์ชัน ใน เพื่อรวมการเปลี่ยนชื่อไฟล์ถ้ามีการตั้งค่าแฟล็ก: inject injector.py def inject(target_files, replacement_map, base_path): for target_file in target_files: for filename in glob.glob(target_file.glob): inject_envs(filename, replacement_map) if target_file.should_be_renamed: filename, new_filename = rename_file(filename) replace_filename_imports(filename, new_filename, base_path) ผลลัพธ์คือไฟล์อาร์ติแฟกต์จะปฏิบัติตามรูปแบบการตั้งชื่อนี้: . . . <origin-file-name> <injection-hash> <extension> ชื่อไฟล์ก่อนฉีด : ชื่อไฟล์หลังฉีด: ตัวแปรสภาพแวดล้อมเดียวกันจะให้ชื่อไฟล์เดียวกัน ทำให้เบราว์เซอร์ของผู้ใช้สามารถแคชไฟล์ได้อย่างถูกต้อง ขณะนี้มีการรับประกันว่าค่าที่ถูกต้องของตัวแปรเหล่านี้จะถูกเก็บไว้ในแคชของเบราว์เซอร์ ส่งผลให้ไคลเอนต์มีประสิทธิภาพดีขึ้น โซลูชันสำหรับการใช้งานที่มีประสิทธิภาพ แนวทางแบบดั้งเดิมในการสร้างแบบแยกกันสำหรับแต่ละสภาพแวดล้อมทำให้เกิดประสิทธิภาพที่ไม่ดีอย่างร้ายแรงบางประการ ซึ่งอาจเป็นปัญหาสำหรับทีมที่มีทรัพยากรจำกัด ตอนนี้คุณมีโครงร่างสำหรับกระบวนการเผยแพร่ที่สามารถแก้ปัญหาระยะเวลาการใช้งานที่ยืดเยื้อ การสร้างที่มากเกินไป และความเสี่ยงที่เพิ่มขึ้นในการรับรองคุณภาพสำหรับแอปพลิเคชันส่วนหน้า ทั้งหมดนี้ ขณะเดียวกันก็แนะนำระดับใหม่ของความสอดคล้องที่รับประกันได้ในทุกสภาพแวดล้อม แทนที่จะต้องใช้ N builds คุณจะต้องใช้เพียงอันเดียว สำหรับรุ่นที่จะออกในเร็วๆ นี้ คุณสามารถปรับใช้ build ที่ได้รับการทดสอบแล้วได้ ซึ่งจะช่วยแก้ไขปัญหาจุดบกพร่องที่อาจเกิดขึ้นได้ เนื่องจาก build เดียวกันจะถูกใช้ในทุกสภาพแวดล้อม นอกจากนี้ ความเร็วในการดำเนินการของสคริปต์นี้ยังเร็วกว่า build ที่ได้รับการปรับให้เหมาะสมที่สุดอย่างไม่มีใครเทียบได้ ตัวอย่างเช่น เกณฑ์มาตรฐานภายในเครื่องบน MacBook 14 PRO, M1, 32GB มีดังนี้: แนวทางของฉันช่วยลดความซับซ้อนของกระบวนการเผยแพร่ รักษาประสิทธิภาพของแอปพลิเคชันโดยอนุญาตให้มีกลยุทธ์การแคชที่มีประสิทธิภาพ และรับรองว่าจุดบกพร่องที่เกี่ยวข้องกับการสร้างจะไม่ปรากฏอยู่ในสภาพแวดล้อม นอกจากนี้ เวลาและความพยายามทั้งหมดที่เคยใช้ไปกับงานสร้างที่น่าเบื่อหน่ายสามารถมุ่งเน้นไปที่การสร้างประสบการณ์ผู้ใช้ที่ดีขึ้นได้ มีอะไรจะไม่ชอบ? เรารับประกันว่าข้อบกพร่องที่เกี่ยวข้องกับการสร้างจะไม่แทรกซึมเข้าไปในแอปสำหรับสภาพแวดล้อมอื่น อาจมีข้อบกพร่องแฝงที่ปรากฏขึ้นเนื่องจากข้อบกพร่องในระบบการสร้าง โอกาสเป็นไปได้น้อยมาก แต่มีอยู่จริง