首頁 web前端 js教程 Parcel原始碼的詳細分析(附範例)

Parcel原始碼的詳細分析(附範例)

Mar 18, 2019 am 10:41 AM
javascript 前端 原始碼

這篇文章帶給大家的內容是關於Parcel原始碼的詳細分析(附範例),有一定的參考價值,有需要的朋友可以參考一下,希望對你有幫助。

這篇文章是對 Parce 的原始碼解析,程式碼基本架構與執行流程,在這之前你如果對 parcel 不熟悉可以先到Parcel官網了解

#介紹

下面是偷懶從官網抄下來的介紹:

極速零設定Web應用打包工具極速打包

Parcel 使用 worker 進程去啟用多核心編譯。同時有檔案系統緩存,即使在重新啟動建置後也能快速再編譯。

將你所有的資源打包

Parcel 具備開箱即用的對 JS, CSS, HTML, 文件 及更多的支持,而且不需要插件。

自動轉換

如若有需要,Babel, PostCSS, 和 PostHTML 甚至 node_modules 套件會被用於自動轉換碼.

零設定碼分拆

使用動態 import() 語法, Parcel 將你的輸出檔案束(bundles)分拆,因此你只需要在初次載入時載入你所需要的程式碼。

熱模組替換

Parcel 不需要配置,在開發環境的時候會自動在瀏覽器內隨著你的程式碼更改而去更新模組。

友善的錯誤日誌

當遇到錯誤時,Parcel 會輸出 語法高亮的程式碼片段,幫助你定位問題。

包裝工具
#browserify 22.98 s
webpack 20.71s
parcel 9.98s
#parcel - with cache 2.64s
#

打包工具

我們常用的打包工具大致功能:

模組化(程式碼的拆分, 合併, Tree-Shaking 等)編譯(es6,7,8  sass typescript 等)壓縮(js, css, html包含圖片的壓縮)HMR (熱替換)

version

parcel-bundler 版本:

"version": "1.11.0 "

檔案架構

|-- assets          资源目录 继承自 Asset.js
|-- builtins        用于最终构建
|-- packagers       打包
|-- scope-hoisting  作用域提升 Tree-Shake
|-- transforms      转换代码为 AST
|-- utils           工具
|-- visitors        遍历 js AST树 收集依赖等

|-- Asset.js          资源
|-- Bundle.js         用于构建 bundle 树
|-- Bundler.js        主目录  
|-- FSCache.js        缓存
|-- HMRServer.js      HMR服务器提供 WebSocket
|-- Parser.js         根据文件扩展名获取对应 Asset
|-- Pipeline.js       多线程执行方法
|-- Resolver.js       解析模块路径
|-- Server.js         静态资源服务器
|-- SourceMap.js      SourceMap
|-- cli.js            cli入口 解析命令行参数
|-- worker.js         多线程入口
登入後複製

流程

說明

Parcel是資源導向的,JavaScript,CSS,HTML 這些都是資源,並不是 webpack 中 js 是一等公民,Parcel 會自動的從入口檔案開始分析這些檔案和模組中的依賴,然後建構一個 bundle 樹,並對其進行打包輸出到指定目錄

#一個簡單的例子

我們從一個簡單的例子開始了解 parcel 內部源碼與流程

index.html
  |-- index.js
    |-- module1.js
    |-- module2.js
登入後複製

上面是我們例子的結構,入口為 index.html, 在 index.html 中我們用script 標籤引用了 src/index.js,在 index.js 中我們引入了2個子模組

執行

npx parcel index.html 或 ./node_modules/.bin/parcel index. html,或用 npm script

cli

"bin": {
    "parcel": "bin/cli.js"
}
登入後複製

檢視 parcel-bundler的 package.json 找到 bin/cli.js,在cli.js裡又指向 ../src/cli

const program = require('commander');

program
  .command('serve [input...]') // watch build
  ...
  .action(bundle);

program.parse(process.argv);

async function bundle(main, command) {
  const Bundler = require('./Bundler');

  const bundler = new Bundler(main, command);

  if (command.name() === 'serve' && command.target === 'browser') {
    const server = await bundler.serve();

    if (server && command.open) {...启动自动打开浏览器}
  } else {
    bundler.bundle();
  }
}
登入後複製

在 cli.js 中利用 commander 解析指令列並呼叫 bundle 方法
有 serve, watch, build 3個指令來呼叫 bundle 函數,因此執行的是pracel index.html 默認為.serve 方法

進入 Bundler.js

bundler.serve

async serve(port = 1234, https = false, host) {
    this.server = await Server.serve(this, port, host, https);
    try {
      await this.bundle();
    } catch (e) {}
    return this.server;
  }
登入後複製

bundler.serve 方法呼叫 serveStatic 起了一個靜態服務指向最終打包的資料夾
#下面是重要的 bundle 方法

bundler.bundle

async bundle() {
    // 加载插件 设置env 启动多线程 watcher hmr
    await this.start();

    if (isInitialBundle) {
      // 创建 输出目录
      await fs.mkdirp(this.options.outDir);

      this.entryAssets = new Set();
      for (let entry of this.entryFiles) {
          let asset = await this.resolveAsset(entry);
          this.buildQueue.add(asset);
          this.entryAssets.add(asset);
      }
    }

    // 打包队列中的资源
    let loadedAssets = await this.buildQueue.run();

    // findOrphanAssets 获取所有资源中独立的没有父Bundle的资源
    let changedAssets = [...this.findOrphanAssets(), ...loadedAssets];

    // 因为接下来要构建 Bundle 树,先对上一次的 Bundle树 进行 clear 操作
    for (let asset of this.loadedAssets.values()) {
      asset.invalidateBundle();
    }

    // 构建 Bundle 树
    this.mainBundle = new Bundle();
    for (let asset of this.entryAssets) {
      this.createBundleTree(asset, this.mainBundle);
    }

    // 获取新的最终打包文件的url
    this.bundleNameMap = this.mainBundle.getBundleNameMap(
      this.options.contentHash
    );
    // 将代码中的旧文件url替换为新的
    for (let asset of changedAssets) {
      asset.replaceBundleNames(this.bundleNameMap);
    }

    // 将改变的资源通过websocket发送到浏览器
    if (this.hmr && !isInitialBundle) {
      this.hmr.emitUpdate(changedAssets);
    }

    // 对资源打包
    this.bundleHashes = await this.mainBundle.package(
      this,
      this.bundleHashes
    );

    // 将独立的资源删除
    this.unloadOrphanedAssets();

    return this.mainBundle;
  }
登入後複製

我們一步一步先從 this.start 看

start

if (this.farm) {
  return;
}

await this.loadPlugins();

if (!this.options.env) {
  await loadEnv(Path.join(this.options.rootDir, 'index'));
  this.options.env = process.env;
}

if (this.options.watch) {
  this.watcher = new Watcher();
  this.watcher.on('change', this.onChange.bind(this));
}

if (this.options.hmr) {
  this.hmr = new HMRServer();
  this.options.hmrPort = await this.hmr.start(this.options);
}

this.farm = await WorkerFarm.getShared(this.options, {
  workerPath: require.resolve('./worker.js')
  });
登入後複製

start:

開頭的判斷 防止多次執行,也就是說 this.start 只會執行一次loadPlugins 載入插件,找到 package.json 檔案 dependencies, devDependencies 中 parcel-plugin-開頭的插件來呼叫loadEnv dotenv-expand 套件將 env.development.local, .env.development, .env.local, .env 擴充至 process.envwatch 初始化監聽檔案並綁定   來監聽文件改變hmr 起一個服務,WebSocket 向瀏覽器發送更改的資源farm 初始化多進程並指定 werker 工作文件,開啟多個 child_process 去解析編譯資源

接下來回到 bundle,isInitialBundle 是一個判斷是否為第一次建置
fs.mkdirp 建立輸出資料夾
遍歷入口文件,透過 resolveAsset,內部呼叫 resolver 解析路徑,並 getAsset 取得對應的 asset(這裡我們入口是 index.html,根據副檔名取得的是 HTMLAsset)
將 asset 加入進隊列
然後啟動 this.buildQueue.run() 對資源從入口遞歸開始打包

PromiseQueue




#PromiseQueue


ShQueue

PromiseQueue

##S 也許這裡打包######PromiseQueue######o是一個 PromiseQueue 非同步隊列###PromiseQueue 在初始化的時候傳入一個回呼函數 callback,內部維護一個參數隊列 queue,add 往隊列裡 push 一個參數,run 的時候while遍歷隊列)),佇列全部執行完畢 Promise 置為完成(resolved)(可以將其理解為 Promise.all)###這裡定義的回呼函數是 processAsset,參數就是入口檔案 index.html 的 HTMLAsset###
async processAsset(asset, isRebuild) {
  if (isRebuild) {
    asset.invalidate();
    if (this.cache) {
      this.cache.invalidate(asset.name);
    }
  }

  await this.loadAsset(asset);
}
登入後複製
### #processAsset 函數內先判斷是否為 Rebuild ,是第一次構建,還是 watch 監聽檔案變更進行的重建,如果是重建則對資源的屬性重置,並使其快取失效###之後調用 loadAsset 加載資源編譯資源######loadAsset###
async loadAsset(asset) {
    if (asset.processed) {
      return;
    }

    // Mark the asset processed so we don't load it twice
    asset.processed = true;

    // 先尝试读缓存,缓存没有在后台加载和编译
    asset.startTime = Date.now();
    let processed = this.cache && (await this.cache.read(asset.name));
    let cacheMiss = false;
    if (!processed || asset.shouldInvalidate(processed.cacheData)) {
      processed = await this.farm.run(asset.name);
      cacheMiss = true;
    }

    asset.endTime = Date.now();
    asset.buildTime = asset.endTime - asset.startTime;
    asset.id = processed.id;
    asset.generated = processed.generated;
    asset.hash = processed.hash;
    asset.cacheData = processed.cacheData;

    // 解析和加载当前资源的依赖项
    let assetDeps = await Promise.all(
      dependencies.map(async dep => {
          dep.parent = asset.name;
          let assetDep = await this.resolveDep(asset, dep);
          if (assetDep) {
            await this.loadAsset(assetDep);
          }
          return assetDep;
      })
    );

    if (this.cache && cacheMiss) {
      this.cache.write(asset.name, processed);
    }
  }
登入後複製
###loadAsset 在開始有個判斷防止重複編譯###之後去讀緩存,讀取失敗就調用 this.farm.run 在多進程裡編譯資源## #編譯完就去載入並編譯依賴的檔案###最後如果是新的資源沒有用到緩存,就重新設定快取###下面說這裡嗎涉及的兩個東西:快取 FSCache 和多進程 WorkerFarm ######FSCache###

read 读取缓存,并判断最后修改时间和缓存的修改时间
write 写入缓存

Parcel原始碼的詳細分析(附範例)

缓存目录为了加速读取,避免将所有的缓存文件放在一个文件夹里,parcel 将 16进制 两位数的 256 种可能创建为文件夹,这样存取缓存文件的时候,将目标文件路径 md5 加密转换为 16进制,然后截取前两位是目录,后面几位是文件名

WorkerFarm

在上面 start 里初始化 farm 的时候,workerPath 指向了 worker.js 文件,worker.js 里有两个函数,init 和 run
WorkerFarm.getShared 初始化的时候会创建一个 new WorkerFarm ,调用 worker.js 的 init 方法,根据 cpu 获取最大的 Worker 数,并启动一半的子进程
farm.run 会通知子进程执行 worker.js 的 run 方法,如果进程数没有达到最大会再次开启一个新的子进程,子进程执行完毕后将 Promise状态更改为完成
worker.run -> pipeline.process -> pipeline.processAsset -> asset.process
Asset.process 处理资源:

async process() {
    if (!this.generated) {
      await this.loadIfNeeded();
      await this.pretransform();
      await this.getDependencies();
      await this.transform();
      this.generated = await this.generate();
    }

    return this.generated;
  }
登入後複製

将上面的代码内部扩展一下:

async process() {
  // 已经有就不需要编译
  if (!this.generated) {
    // 加载代码
    if (this.contents == null) {
      this.contents = await this.load();
    }
    // 可选。在收集依赖之前转换。
    await this.pretransform();
    // 将代码解析为 AST 树
    if (!this.ast) {
      this.ast = await this.parse(this.contents);
    }
    // 收集依赖
    await this.collectDependencies();
    // 可选。在收集依赖之后转换。
    await this.transform();
    // 生成代码
    this.generated = await this.generate();
  }

  return this.generated;
}

// 最后处理代码
async postProcess(generated) {
  return generated
}
登入後複製

processAsset 中调用 asset.process 生成 generated 这个generated 不一定是最终代码 ,像 html里内联的 script ,vue 的 html, js, css,都会进行二次或多次递归处理,最终调用 asset.postProcess 生成代码

Asset

下面说几个实现

HTMLAsset:

pretransform 调用 posthtml 将 html 解析为 PostHTMLTree(如果没有设置posthtmlrc之类的不会走)

parse 调用 posthtml-parser 将 html 解析为 PostHTMLTree

collectDependencies 用 walk 遍历 ast,找到 script, img 的 src,link 的 href 等的地址,将其加入到依赖

transform htmlnano 压缩代码

generate 处理内联的 script 和 css

postProcess posthtml-render 生成 html 代码

JSAsset:

pretransform 调用 @babel/core 将 js 解析为 AST,处理 process.env

parse 调用 @babel/parser 将 js 解析为 AST

collectDependencies 用 babylon-walk 遍历 ast, 如 ImportDeclaration,import xx from 'xx' 语法,CallExpression 找到 require调用,import 被标记为 dynamic 动态导入,将这些模块加入到依赖

transform 处理 readFileSync,__dirname, __filename, global等,如果没有设置scopeHoist 并存在 es6 module 就将代码转换为 commonjs,terser 压缩代码

generate @babel/generator 获取 js 与 sourceMap 代码

VueAsset:

parse @vue/component-compiler-utils 与 vue-template-compiler 对 .vue 文件进行解析

generate 对 html, js, css 处理,就像上面说到会对其分别调用 processAsset 进行二次解析

postProcess component-compiler-utils 的 compileTemplate, compileStyle处理 html,css,vue-hot-reload-api HMR处理,压缩代码

回到 bundle 方法:

let loadedAssets = await this.buildQueue.run() 就是上面说到的PromiseQueue 和 WorkerFarm 结合起来:buildQueue.run —> processAsset -> loadAsset -> farm.run -> worker.run -> pipeline.process -> pipeline.processAsset -> asset.process,执行之后所有资源编译完毕,并返回入口资源loadedAssets就是 index.html 对应的 HTMLAsset 资源

之后是 let changedAssets = [...this.findOrphanAssets(), ...loadedAssets] 获取到改变的资源

findOrphanAssets 是从所有资源中查找没有 parentBundle 的资源,也就是独立的资源,这个 parentBundle 会在等会的构建 Bundle 树中被赋值,第一次构建都没有 parentBundle,所以这里会重复入口文件,这里的 findOrphanAssets 的作用是在第一次构建之后,文件change的时候,在这个文件 import了新的一个文件,因为新文件没有被构建过 Bundle 树,所以没有 parentBundle,这个新文件也被标记物 change

invalidateBundle 因为接下来要构建新的树所以调用重置所有资源上一次树的属性

createBundleTree 构建 Bundle 树:

首先一个入口资源会被创建成一个 bundle,然后动态的 import() 会被创建成子 bundle ,这引发了代码的拆分。

当不同类型的文件资源被引入,兄弟 bundle 就会被创建。例如你在 JavaScript 中引入了 CSS 文件,那它会被放置在一个与 JavaScript 文件对应的兄弟 bundle 中。

如果资源被多于一个 bundle 引用,它会被提升到 bundle 树中最近的公共祖先中,这样该资源就不会被多次打包。

Bundle:

type:它包含的资源类型 (例如:js, css, map, ...)

name:bundle 的名称 (使用 entryAsset 的 Asset.generateBundleName() 生成)

parentBundle:父 bundle ,入口 bundle 的父 bundle 是 null

entryAsset:bundle 的入口,用于生成名称(name)和聚拢资源(assets)

assets:bundle 中所有资源的集合(Set)

childBundles:所有子 bundle 的集合(Set)

siblingBundles:所有兄弟 bundle 的集合(Set)

siblingBundlesMap:所有兄弟 bundle 的映射 Map

offsets:所有 bundle 中资源位置的映射 Map ,用于生成准确的 sourcemap 。

我们的例子会被构建成:

html            ( index.html )
  |-- js        ( index.js, module1.js, module2.js )
    |-- map     ( index.js, module1.js, module2.js )
登入後複製

module1.js 和 module2.js 被提到了与 index.js 同级,map 因为类型不同被放到了 子bundle

一个复杂点的树:

// 资源树
index.html
  |-- index.css
  |-- bg.png
  |-- index.js
    |-- module.js
登入後複製
// mainBundle
html            ( index.html )
  |-- js        ( index.js, module.js )
    |-- map     ( index.map, module.map )
  |-- css       ( index.css )
    |-- js      ( index.css, css-loader.js bundle-url.js )
    |-- map     ( css-loader.js, bundle-url.js )
  |-- png       ( bg.png )
登入後複製

因为要对 css 热更新,所以新增了 css-loader.js, bundle-url.js 两个 js

replaceBundleNames替换引用:生成树之后将代码中的文件引用替换为最终打包的文件名,如果是生产环境会替换为 contentHash 根据内容生成 hash

hmr更新: 判断启用 hmr 并且不是第一次构建的情况,调用 hmr.emitUpdate 将改变的资源发送给浏览器

Bundle.package 打包

unloadOrphanedAssets 将独立的资源删除

package

package 将generated 写入到文件
有6种打包:
CSSPackager,HTMLPackager,SourceMapPackager,JSPackager,JSConcatPackager,RawPackager
当开启 scopeHoist 时用 JSConcatPackager 否则 JSPackager
图片等资源用 RawPackager

最终我们的例子被打包成 index.html, src.[hash].js, src.[hash].map 3个文件

index.html 里的 js 路径被替换成立最终打包的地址

我们看一下打包的 js:

parcelRequire = (function (modules, cache, entry, globalName) {
  // Save the require from previous bundle to this closure if any
  var previousRequire = typeof parcelRequire === 'function' && parcelRequire;
  var nodeRequire = typeof require === 'function' && require;

  function newRequire(name, jumped) {
    if (!cache[name]) {
      localRequire.resolve = resolve;
      localRequire.cache = {};

      var module = cache[name] = new newRequire.Module(name);

      modules[name][0].call(module.exports, localRequire, module, module.exports, this);
    }

    return cache[name].exports;

    function localRequire(x){
      return newRequire(localRequire.resolve(x));
    }

    function resolve(x){
      return modules[name][4][x] || x;
    }
  }
  for (var i = 0; i <p>可以看到代码被拼接成了对象的形式,接收参数 module, require 用来模块导入导出,实现了 commonjs 的模块加载机制,一个更加简化版:</p><pre class="brush:php;toolbar:false">parcelRequire = (function (modules, cache, entry, globalName) {
  function newRequire(id){
    if(!cache[id]){
      let module = cache[id] = { exports: {} }
      modules[id][0].call(module.exports, newRequire, module, module.exports, this);
    }
    return cache[id]
  }
  for (var i = 0; i <p>代码被拼接起来:</p><pre class="brush:php;toolbar:false">`(function(modules){
  //...newRequire
})({` +
  asset.id +
    ':[function(require,module,exports) {\n' +
        asset.generated.js +
      '\n},' +
'})'
登入後複製
(function(modules){
  //...newRequire
})({
  "src/index.js":[function(require,module,exports){
    // code
  }]
})
登入後複製

hmr-runtime

上面打包的 js 中还有个 hmr-runtime.js 太长被我省略了
hmr-runtime.js 创建一个 WebSocket 监听服务端消息
修改文件触发 onChange 方法,onChange 将改变的资源 buildQueue.add 加入构建队列,重新调用 bundle 方法,打包资源,并调用 emitUpdate 通知浏览器更新
当浏览器接收到服务端有新资源更新消息时
新的资源就会设置或覆盖之前的模块
modules[asset.id] = new Function('require', 'module', 'exports', asset.generated.js)
对模块进行更新:

function hmrAccept(id){
  // dispose 回调
  cached.hot._disposeCallbacks.forEach(function (cb) {
    cb(bundle.hotData);
  });

  delete bundle.cache[id]; // 删除之前缓存
  newRequire(id); // 重新此加载

  // accept 回调
  cached.hot._acceptCallbacks.forEach(function (cb) {
    cb();
  });

  // 递归父模块 进行更新
  getParents(global.parcelRequire, id).some(function (id) {
    return hmrAccept(global.parcelRequire, id);
  });
}
登入後複製

至此整个打包流程结束

总结

parcle index.html
进入 cli,启动Server调用 bundle,初始化配置(Plugins, env, HMRServer, Watcher, WorkerFarm),从入口资源开始,递归编译(babel, posthtml, postcss, vue-template-compiler等),编译完设置缓存,构建 Bundle 树,进行打包
如果没有 watch 监听,结束关闭 Watcher, Worker, HMR
有 watch 监听:
文件修改,触发 onChange,将修改的资源加入构建队列,递归编译,查找缓存(这一步缓存的作用就提醒出来了),编译完设置新缓存,构建 Bundle 树,进行打包,将 change 的资源发送给浏览器,浏览器接收 hmr 更新资源

以上是Parcel原始碼的詳細分析(附範例)的詳細內容。更多資訊請關注PHP中文網其他相關文章!

本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

AI Hentai Generator

AI Hentai Generator

免費產生 AI 無盡。

熱門文章

R.E.P.O.能量晶體解釋及其做什麼(黃色晶體)
3 週前 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳圖形設置
3 週前 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.如果您聽不到任何人,如何修復音頻
4 週前 By 尊渡假赌尊渡假赌尊渡假赌
WWE 2K25:如何解鎖Myrise中的所有內容
1 個月前 By 尊渡假赌尊渡假赌尊渡假赌

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

PHP與Vue:完美搭檔的前端開發利器 PHP與Vue:完美搭檔的前端開發利器 Mar 16, 2024 pm 12:09 PM

PHP與Vue:完美搭檔的前端開發利器在當今網路快速發展的時代,前端開發變得愈發重要。隨著使用者對網站和應用的體驗要求越來越高,前端開發人員需要使用更有效率和靈活的工具來創建響應式和互動式的介面。 PHP和Vue.js作為前端開發領域的兩個重要技術,搭配起來可以稱得上是完美的利器。本文將探討PHP和Vue的結合,以及詳細的程式碼範例,幫助讀者更好地理解和應用這兩

前端面試官常問的問題 前端面試官常問的問題 Mar 19, 2024 pm 02:24 PM

在前端開發面試中,常見問題涵蓋廣泛,包括HTML/CSS基礎、JavaScript基礎、框架和函式庫、專案經驗、演算法和資料結構、效能最佳化、跨域請求、前端工程化、設計模式以及新技術和趨勢。面試官的問題旨在評估候選人的技術技能、專案經驗以及對行業趨勢的理解。因此,應試者應充分準備這些方面,以展現自己的能力和專業知識。

簡易JavaScript教學:取得HTTP狀態碼的方法 簡易JavaScript教學:取得HTTP狀態碼的方法 Jan 05, 2024 pm 06:08 PM

JavaScript教學:如何取得HTTP狀態碼,需要具體程式碼範例前言:在Web開發中,經常會涉及到與伺服器進行資料互動的場景。在與伺服器進行通訊時,我們經常需要取得傳回的HTTP狀態碼來判斷操作是否成功,並根據不同的狀態碼來進行對應的處理。本篇文章將教你如何使用JavaScript來取得HTTP狀態碼,並提供一些實用的程式碼範例。使用XMLHttpRequest

Django是前端還是後端?一探究竟! Django是前端還是後端?一探究竟! Jan 19, 2024 am 08:37 AM

Django是一個由Python編寫的web應用框架,它強調快速開發和乾淨方法。儘管Django是web框架,但要回答Django是前端還是後端這個問題,需要深入理解前後端的概念。前端是指使用者直接和互動的介面,後端是指伺服器端的程序,他們透過HTTP協定進行資料的互動。在前端和後端分離的情況下,前後端程式可以獨立開發,分別實現業務邏輯和互動效果,資料的交

PHP程式碼在瀏覽器中如何顯示原始碼而不被解釋執行? PHP程式碼在瀏覽器中如何顯示原始碼而不被解釋執行? Mar 11, 2024 am 10:54 AM

PHP程式碼在瀏覽器中如何顯示原始碼而不被解釋執行? PHP是一種伺服器端腳本語言,通常用於開發動態網頁。當PHP檔案在伺服器上被要求時,伺服器會解釋執行其中的PHP程式碼,並將最終的HTML內容傳送到瀏覽器以供顯示。然而,有時我們希望在瀏覽器中直接展示PHP檔案的原始碼,而不是被執行。本文將介紹如何在瀏覽器中顯示PHP程式碼的源碼,而不被解釋執行。在PHP中,可以使

Go語言前端技術探秘:前端開發新視野 Go語言前端技術探秘:前端開發新視野 Mar 28, 2024 pm 01:06 PM

Go語言作為一種快速、高效的程式語言,在後端開發領域廣受歡迎。然而,很少有人將Go語言與前端開發聯繫起來。事實上,使用Go語言進行前端開發不僅可以提高效率,還能為開發者帶來全新的視野。本文將探討使用Go語言進行前端開發的可能性,並提供具體的程式碼範例,幫助讀者更了解這一領域。在傳統的前端開發中,通常會使用JavaScript、HTML和CSS來建立使用者介面

網站線上看原始碼 網站線上看原始碼 Jan 10, 2024 pm 03:31 PM

可以使用瀏覽器的開發者工具來查看網站的源代碼,在Google Chrome瀏覽器中:1、開啟Chrome 瀏覽器,造訪要查看原始碼的網站;2、右鍵點選網頁上的任何位置,然後選擇「檢查」或按下快速鍵Ctrl + Shift + I開啟開發者工具;3、在開發者工具的頂部功能表列中,選擇「Elements」標籤;4、看到網站的HTML 和CSS 程式碼即可。

Golang與前端技術結合:探討Golang如何在前端領域發揮作用 Golang與前端技術結合:探討Golang如何在前端領域發揮作用 Mar 19, 2024 pm 06:15 PM

Golang與前端技術結合:探討Golang如何在前端領域發揮作用,需要具體程式碼範例隨著互聯網和行動應用的快速發展,前端技術也愈發重要。而在這個領域中,Golang作為一門強大的後端程式語言,也可以發揮重要作用。本文將探討Golang如何與前端技術結合,以及透過具體的程式碼範例來展示其在前端領域的潛力。 Golang在前端領域的角色作為一門高效、簡潔且易於學習的

See all articles