> 2021年10月23日信息消化 ### How I built a modern website in 2021 origin: [How I built a modern website in 2021](https://kentcdodds.com/blog/how-i-built-a-modern-website-in-2021?ref=sidebar) ##### Stats overview At the time of this writing (October 2021) here are the [`cloc`](https://github.com/AlDanial/cloc) stats: ```bash $ npx cloc ./app ./types ./tests ./styles ./mocks ./cypress ./prisma ./.github 266 text files. 257 unique files. 15 files ignored. github.com/AlDanial/cloc v 1.90 T=0.16 s (1601.9 files/s, 194240.7 lines/s) ------------------------------------------------------------------------------- Language files blank comment code ------------------------------------------------------------------------------- TypeScript 219 2020 583 21582 CSS 10 198 301 4705 JSON 7 0 0 609 YAML 2 43 13 232 SQL 7 20 25 52 JavaScript 4 2 3 42 Markdown 1 0 0 2 TOML 1 0 2 1 ------------------------------------------------------------------------------- SUM: 251 2283 927 27225 ------------------------------------------------------------------------------- ``` And to get a sense of the amount of content I have on this site, here's a word count: ```bash find ./content -type f | xargs wc -w | tail -1 280801 total ``` ##### Technology overview Here are the primary technologies used in this project (in no particular order): - [React](https://reactjs.org/): For the UI - [Remix](https://remix.run/): Framework for the Client/Server/Routing - [TypeScript](https://www.typescriptlang.org/): Typed JavaScript (necessary for any project you plan to maintain) - [XState](https://xstate.js.org/): State machine tool making complex component state management simple - [Prisma](https://www.prisma.io/): Fantastic ORM with stellar migrations and TypeScript client support - [Express](https://expressjs.com/): Node server framework - [Cypress](https://cypress.io/): E2E testing framework - [Jest](https://jestjs.io/): Unit/Component testing framework - [Testing Library](https://testing-library.com/): Simple utilities for testing DOM-based user interfaces - [MSW](https://mswjs.io/): Fantastic tool for mocking HTTP requests in the browser/node - [Tailwind CSS](https://tailwindcss.com/): Utility classes for consistent/maintainable styling - [Postcss](https://postcss.org/): CSS processor (pretty much just use it for autoprefixer and tailwind) - [Reach UI](https://reach.tech/): A set of accessible UI components every app needs (accordion/tabs/dialog/etc...) - [ESBuild](https://esbuild.github.io/): JavaScript bundler (used by Remix and mdx-bundler). - [mdx-bundler](https://github.com/kentcdodds/mdx-bundler): Tool for compiling and bundling MDX for my site content (blog posts and some simple pages). - [Octokit](https://github.com/octokit/octokit.js): Library making interacting with the GitHub API easier. - [Framer Motion](https://www.framer.com/motion/): Great React Animation library - [Unified](https://unifiedjs.com/): Markdown/HTML parser/transformer/compiler system. - [Postgres](https://www.postgresql.org/): Battle tested SQL database - [Redis](https://redis.io/): In-memory database–key/value store. Here are the services this site uses: - [Fly.io](https://fly.io/): Super hosting platform - [GitHub Actions](https://github.com/features/actions): Hosted CI pipeline service - [Sentry](https://sentry.io/): Error reporting service - [Cloudinary](https://cloudinary.com/): Fantastic image hosting and transformation service. - [Fathom](https://kcd.im/fathom): Privacy-focused ethical analytics service. - [Metronome](https://metronome.sh/): Remix metrics service #### Architectural overview ##### Deployment pipeline ![Excalidraw diagram of a deployment pipeline](https://raw.githubusercontent.com/Phalacrocorax/memo-image-host/master/PicGo/deployment-pipeline-dark) First, I commit a change to the local repo. Then I push my changes to the (open source) GitHub repository. From there, I have two GitHub Actions that run automatically on every push to the `main` branch. The "Discord" circle just indicates that I've got a GitHub webhook installed for discord so each success/failure will result in a message in a channel on discord so I can easily keep track of how things are going at any time. #### [GitHub Actions: ???? Refresh Content](https://kentcdodds.com/blog/how-i-built-a-modern-website-in-2021?ref=sidebar#github-actions--refresh-content) The first GitHub action is called "???? **Refresh Content**" and is intended to refresh any content that may have changed. Before describing what it does, let me explain the problem it solves. The previous version of kentcdodds.com was written with Gatsby and due to the SSG nature of Gatsby every time I wanted to make a content change, I would have to rebuild my entire site (which could take anywhere from 10-25 minutes). But now that I have a server and I'm using SSR, I don't have to wait for a complete rebuild to refresh my content. My server can access all the content directly from GitHub via the GitHub API. The problem is that this adds a lot of overhead to each request for my blog posts. Add to that time to compile the MDX code and you've got yourself a really slow blog. So I've got myself a Redis cache for all of this. The issue then is the problem of caches: invalidation. I need to make sure that when I make an update to some content, the Redis cache gets refreshed. And that's what this first GitHub action does. First it determines all content changes that occurred between the commit that's being built and the commit of the last time there was a refresh (that value is stored in redis and my server exposes an endpoint for my action to retrieve it). **If any of the changed files were in the `./content` directory, then the action makes an authenticated POST request to another endpoint on my server with all the content files that were changed.** Then my server retrieves all the content from the GitHub API, recompiles the MDX pages, and pushes the update to the **Redis cache** which Fly.io automatically propagates to the other regions. This reduces what used to take 10-25 minutes down to 8 seconds. And it saves me computational resources as well because fixing a typo in my content doesn't necessitate a rebuild/redeploy/cache bust of the whole site. #### [GitHub Actions: ???? Deploy](https://kentcdodds.com/blog/how-i-built-a-modern-website-in-2021?ref=sidebar#github-actions--deploy) The second GitHub action deploys the site. First, it determines whether the changes are deployable to begin with. **If the only thing that changed was content, then there's no reason to bother redeploying** thanks to the refresh content action. The vast majority of my commits on my old site were content-only changes so this helps save the trees ???????????? Once it's determined that we have deployable changes, then we kick off multiple steps in parallel: - ⬣ ESLint: Linting the project for simple mistakes - ʦ TypeScript: Type checking the project for type errors - ???? Jest: Running component and unit tests - ⚫️ Cypress: Running end-to-end tests - ???? Build: Building the docker image The Cypress step is further parallelized by splitting my E2E tests into three individual containers which split the tests between them to run them as quickly as possible. Once ESLint, TypeScript, Jest, and the Build all successfully complete, then we can move on to the deploy step. On my end this bit is simple. I simply use the Fly CLI to deploy the docker container that was created in the build step. From there Fly takes care of the rest. It starts up the docker container in each of the regions I've configured for my Node server: Dallas, Santiago, Sydney, Hong Kong, Chennai, and Amsterdam. When they're ready to receive traffic, Fly switches traffic to the new instance and then shuts down the old one. **If there's a startup failure in any region, it rolls back automatically.** Additionally, this step of the **deploy uses prisma's migrate feature** to apply any migrations I've created since the last migration (it stores info on the last migration in my Postgres DB). Prisma performs the migration on the Dallas instance of my Postgres cluster and Fly automatically propagates those changes to all other regions instantly. And that's what happens when I say: `git push` or click the "Merge" button ???? ### [Database Connectivity](https://kentcdodds.com/blog/how-i-built-a-modern-website-in-2021?ref=sidebar#database-connectivity) One of the coolest parts of Fly.io (and the reason I chose Fly over alternative Node server hosts) is the ability to deploy your app to multiple regions all over the world. I've chosen 6 based on the analytics from my previous site, but they have many more. Deploying the Node server to multiple regions is only part of the story though. To really get the network performance benefits of colocation, you need your data to be close by as well. So Fly also supports hosting Postgres and Redis clusters in each region as well. This means when an authorized user in Berlin goes to [The Call Kent Podcast](https://kentcdodds.com/calls), they hit the closest server to them (Amsterdam) which will query the Postgres DB and Redis cache that are located in the same region, making the whole experience extremely fast wherever you are in the world. What's more, I don't have to make the **trade-off of vendor lock-in**. At any time I could take my toys home and **host my site anywhere else that supports deploying Docker**. This is why I didn't go with a solution like Cloudflare Workers and FaunaDB. Additionally, I don't have to retrofit/limit my app to the constraints of those services. I'm extremely happy with Fly and don't expect to leave any time soon. But that doesn't mean this is all trade-off free (nothing is). All of this multi-regional deployment comes with the problem of consistency. I've got multiple databases, but I don't want to partition my app by region. The data should be the same in all those databases. So how do I ensure consistency? Well, we choose one region to be our primary region, and then make all other regions read-only. Yup, so the user in Berlin won't be able to write to the database in Amsterdam. But don't worry, **all instances of my Node server will make a read connection to the closest region so reads (by far the most common operation) are fast, and then they also create a write connection to the primary region so writes can work. And as soon as an update happens in the primary region**, Fly automatically and immediately propagates those changes to all other regions. It's very very fast and works quite well! ##### [Local Development with MSW](https://kentcdodds.com/blog/how-i-built-a-modern-website-in-2021?ref=sidebar#local-development-with-msw) When I'm developing locally, I have my postgres and redis databases running in a docker container via a simple `docker-compose.yml`. But I also interact with a bunch of 3rd party APIs. As of the time of this writing (September 2021), my app works with the following third party APIs: 1. `api.github.com` 2. `oembed.com` 3. `api.twitter.com` 4. `api.tito.io` 5. `api.transistor.fm` 6. `s3.amazonaws.com` 7. `discord.com/api` 8. `api.convertkit.com` 9. `api.simplecast.com` 10. `api.mailgun.net` 11. `res.cloudinary.com` 12. `www.gravatar.com/avatar` 13. `verifier.meetchopra.com` Phew! ???? I'm a big believer in being able to work completely offline. It's fun to go up into the mountains with no internet connection and still be able to work on your site (and as I type this, I'm on an airplane without internet). But with so many 3rd party APIs how is this possible? Simple: I mock it with MSW! MSW is a fantastic tool for mocking network requests in both the browser and node. For me, 100% of my 3rd party network requests happen in Remix loaders on the server, so I only have MSW setup in my node server. What I love about MSW is that it's completely nonintrusive on my codebase. The way I get it running is pretty simple. Here's how I start my server: `node .` And here's how I start it with mocks enabled: `node --require ./mocks .` That's it. The `./mocks` directory holds all my MSW handlers and initializes MSW to intercept HTTP requests made by the server. Now I'm not going to say it was easy writing the mocks for all of these services, it's a fair amount of code and took me a bit of time. But boy it's really helped me stay productive. My mock is much faster than the API and doesn't rely on my internet connection one bit. It's a huge win and I strongly recommend it. > MSW is an enormous productivity and confidence booster for me. ##### [Caching with Redis/LRU](https://kentcdodds.com/blog/how-i-built-a-modern-website-in-2021?ref=sidebar#caching-with-redislru) As described earlier with the architecture diagrams, I host my redis cache with Fly.io. It's phenomenal. But I've built my own little abstraction for interacting with redis to have some interesting qualities that I think are worth talking about. First, the problems: I want my site to be super fast, but I also want to do things on each request that take time. Some things I want to do could even be described as slow or unreliable. So I use Redis to cache things. This can take something that takes 350ms down to 5ms. However, with caching comes the complication of cache invalidation. I've described how I do this with my content, but I'm caching a lot more than that. **Most of my 3rd party APIs are cached and even the results of a few of my Postgres queries are cached** (Postgres is pretty fast, but on my blog I execute ~30 queries on every page). Not everything is cached in Redis either, some things are cached via the `lru-cache` module (lru stands for "least-recently-used" and helps your cache avoid out of memory errors). I use the in-memory LRU cache for very short-lived cache values like the postgres queries. With so many things that need to be cached, an abstraction was needed to make the invalidation process simpler and consistent. I was too impatient to find a library that would work for me, so I just built my own. Here's the API: ```js type CacheMetadata = { createdTime: number maxAge: number | null } // it's the value/null/undefined or a promise that resolves to that type VNUP = Value | null | undefined | Promise async function cachified< Value, Cache extends { name: string get: (key: string) => VNUP<{ metadata: CacheMetadata value: Value }> set: ( key: string, value: { metadata: CacheMetadata value: Value }, ) => unknown | Promise del: (key: string) => unknown | Promise }, >(options: { key: string cache: Cache getFreshValue: () => Promise checkValue?: (value: Value) => boolean forceFresh?: boolean | string request?: Request fallbackToCache?: boolean timings?: Timings timingType?: string maxAge?: number }): Promise { // do the stuff... } // here's an example of the cachified credits.yml that powers the /credits page: async function getPeople({ request, forceFresh, }: { request?: Request forceFresh?: boolean | string }) { const allPeople = await cachified({ cache: redisCache, key: 'content:data:credits.yml', request, forceFresh, maxAge: 1000 * 60 * 60 * 24 * 30, getFreshValue: async () => { const creditsString = await downloadFile('content/data/credits.yml') const rawCredits = YAML.parse(creditsString) if (!Array.isArray(rawCredits)) { console.error('Credits is not an array', rawCredits) throw new Error('Credits is not an array.') } return rawCredits.map(mapPerson).filter(typedBoolean) }, checkValue: (value: unknown) => Array.isArray(value), }) return allPeople } ``` That's a lot of options ???? But don't worry, I'll walk you through them. Let's start with the generic types: - `Value` refers to the value that should be stored/retrieved from the cache - `Cache` is just an object that has a `name` (for logging), and `get`, `set`, and `del` methods. - `CacheMetadata` is info that gets saved along with the value for determining when the value should be refreshed. ##### [Image optimization with Cloudinary](https://kentcdodds.com/blog/how-i-built-a-modern-website-in-2021?ref=sidebar#image-optimization-with-cloudinary) Ok folks... Cloudinary is incredible. **All the images on this site are hosted on cloudinary and then delivered to your browser in the perfect size and format for your device.** It took a little work (and a lot of money... Cloudinary is not cheap) to make this magic happen, but it's saving a TON of internet bandwidth for you and makes the images load much faster. One of the reasons my Gatsby site took so long to build was that every time I ran the build, **gatsby had to generate all the sizes for all my images.** The Gatsby team helped me put together a persistent cache, but if I ever needed to bust that cache then I'd have to run Netlify a few times (it would timeout) to fill up the cache again so I could deploy my site again ???? With Cloudinary, I don't have that problem. I just upload the photo, reference the cloudinary ID in my mdx, and then my site generates the right `sizes` and `srcset` props for the `` tag. Because Cloudinary allows transforms in the URL, I'm able to generate an image that's exactly the dimensions I want for those props. Another cool thing I'm doing that you may have noticed on the blog posts is on the server I make a request for the banner image that's only `100px` wide with a `blur` transform. Then I convert that into a base64 string. This is cached along with the other metadata about the post. Then when I server-render the post, I server-render the base64 blurred image scaled up (I also use `backdrop-filter` with CSS to smooth it out a bit from the upscale) and then fade-in the full-size image when it's finished loading. I'm pretty darn happy with this approach. > Cloudinary blows my mind and I'm happy to pay the cost for what I get from it. ##### [MDX Compilation with mdx-bundler](https://kentcdodds.com/blog/how-i-built-a-modern-website-in-2021?ref=sidebar#mdx-compilation-with-mdx-bundler) I've been using MDX to write my blog posts ever [since I left Medium](https://kentcdodds.com/blog/goodbye-medium). I really love that I can easily have interactive bits in the middle of my blog posts without having to handle them in any special way in the code of my site. As one might expect, I do have several unified plugins (remark/rehype) to automate some things for me during compilation of the mdx. I have one for auto-adding affiliate query params for amazon and egghead links. I have another for converting a link to a tweet into a completely custom twitter embed (way faster than using the twitter widget thing) and one for converting egghead video links into video embeds. I've got another custom one (borrowed from a secret package by Ryan Florence) for syntax highlighting based on Shiki, and one for optimizing inline cloudinary images. ### Ideologies of Delayed Informatization origin: [Ideologies of Delayed Informatization](https://theamericansun.com/2019/08/27/ideologies-of-delayed-informatization/) We’re observing the emergence of new (old) ideologies, and rapid political realignments punctuated by heroic calls for action and decisions. How do we deal with it? Let me take you through some old-school sociology before looking at the **latest ideological calls for “decisions”** and **the suppression of speech**. This is necessary to understand and navigate the online wars, the i**nternet upheavals** and **strange ideological quarrels** of our day. 我们正在观察新(旧)的意识形态的出现,以及快速的政治调整,其中夹杂着对行动和决策的英雄式呼吁。我们该如何应对?在看最新的 "决定 "和压制言论的意识形态呼吁之前,让我带你了解一些老式的社会学。这对于理解和驾驭我们这个时代的网络战争、网络动荡和奇怪的意识形态争吵是必要的。 I’m a simple man who has learned to **distrust ideologies** and ideologues over the years. By material circumstances (or moral inadequacy), I was forced to confront existence without the luxury of being able to maintain ideological consistency. The result is that when I hear someone making ideological claims, I no longer just pay close attention to the content of their ideas but also to the problems they’re trying to solve on a personal and institutional level. This approach often exposes their motivations, the amount of power they have, their attitude toward me, and what I can expect from the implementation of their ideological imperatives. This isn’t materialism or historicism, by the way. I don’t claim ideological propositions necessarily lack truth. This is more a mindset I’ve adopted to survive and protect myself against the harmful consequences of ideologies. The debates about the good and the true probably are still valuable but, **depending on the context of the ideology, I’ve learned it’s often better to scrutinize the motivations and station of the ideologue.** 我是一个简单的人,多年来学会了不信任意识形态和思想家。由于物质环境(或道德上的不足),我被迫面对生存,而不能奢望能够保持意识形态的一致性。其结果是,当我听到有人提出意识形态的主张时,我不再只是密切关注他们的思想内容,还关注他们在个人和机构层面试图解决的问题。这种方法往往暴露了他们的动机,他们拥有多少权力,他们对我的态度,以及我可以从他们的意识形态要求的实施中期待什么。顺便说一下,这不是唯物主义或历史主义。我并不声称意识形态的命题一定缺乏真理。这更像是我为了生存和保护自己免受意识形态的有害后果而采取的一种心态。关于真善美的辩论可能仍然是有价值的,但根据意识形态的背景,我已经了解到,仔细检查意识形态的动机和站位往往更好。 Several years ago, a sociologist named **Mary Matossian** wrote a short, schematic article on the morphology of “*Ideologies of Delayed Industrialization*,” in which she identified structural similarities among seemingly contradictory ideologies ranging from Marxism-Leninism, Shintoism, Kemalism, Fascism, Nehruhism, all the way to Gandhism. Matossian speculated that the impetus behind these ideologies and the cause of their similarities related to common psychological and social context variables present at the foundation of each ideology. 几年前,一位名叫玛丽-马托西恩的社会学家写了一篇关于 "延迟工业化的意识形态 "形态学的短文,其中她指出了看似矛盾的意识形态之间的结构相似性,从马克思列宁主义、神道教、凯末尔主义、法西斯主义、尼赫鲁主义,一直到甘地主义。Matossian推测,这些意识形态背后的动力和它们的相似性的原因与每种意识形态的基础上存在的共同心理和社会环境变量有关。 The variables were an undeveloped community with exposure to the western industrialization and modernization technological cultural package for a certain amount of time, a partially westernized native educated class, and the psychological needs of the native intellectual. 这些变量是一个不发达的社区,在一定时间内接触到西方工业化和现代化的技术文化包,一个部分西方化的本土教育阶层,以及本土知识分子的心理需求。 In the presence of such variables, there emerges an ideal type: **the Assaulted Intellectual**. The Assaulted intellectual is torn between two worlds: the modern west and his traditional native culture. He is partially of the west because of his education and is therefore unable to completely abandon it. He is also partially rooted in his traditional culture, usually with a higher station than his average fellow citizen, and because of this position feels compelled to defend it against the assault of centuries of western scientific, technological, legal, social, artistic, and religious innovation. 在这些变量的存在下,出现了一种理想的类型:被攻击的知识分子。被攻击的知识分子在两个世界之间挣扎:现代西方和他的传统本土文化。由于他所受的教育,他部分地属于西方,因此无法完全放弃西方。他也部分地扎根于他的传统文化,通常比他的普通同胞有更高的地位,由于这种地位,他感到不得不捍卫它,以抵御几个世纪以来西方科学、技术、法律、社会、艺术和宗教创新的攻击。 The modern western package, comprising everything from jazz to air conditioning, hurts the Assaulted Intellectual’s ego by juxtaposing his culture and its claims to centrality and omnipotence with the stark, contradictory reality of western cultural superiority. Confrontation with the industrialized west results in the destruction of his traditional institutions and values, forcing him to reorient his relationship to the west, his nation’s past, and the masses of his own people. 现代西方的包装,包括从爵士乐到空调的一切,通过将他的文化及其对中心地位和无所不能的主张与西方文化优越性的严酷、矛盾的现实并列,伤害了受攻击的知识分子的自我。与西方工业化的对抗导致了他的传统体制和价值观的破坏,迫使他重新定位他与西方、他的国家的过去以及他自己的人民群众的关系。 The **reorientation process** places the Assaulted Intellectual in an uneasy and ambiguous position. He is ashamed of the socio-economic conditions of his own people relative to the west and feels defensive, as if something must be done to modernize his country and restore its pride. Being a creature partially of the west, his attitude toward himself remains ambivalent as he agonizes over his inauthentic mongrel nature. 重新定位的过程使受攻击的知识分子处于一个不安和模糊的位置。他对自己的人民相对于西方的社会经济状况感到羞愧,并感到防卫,似乎必须做些什么来使他的国家现代化并恢复其自豪感。作为部分来自西方的生物,他对自己的态度仍然是矛盾的,因为他为自己不真实的杂种性质感到痛苦。 Because science and technology – the domain of the west – cannot repair his ego, he seeks solace elsewhere through ideology. His ideology is at once a rationalization for his people’s asymmetric development relative to the west and a call to action, and is necessarily utopian because it focuses on “*changing a social order that is already changing*.” 由于科学和技术--西方的领域--无法修复他的自我,他通过意识形态在其他地方寻求慰藉。他的意识形态既是他的人民相对于西方的不对称发展的合理化,也是对行动的呼吁,而且必然是乌托邦式的,因为它侧重于 "改变一个已经在变化的社会秩序"。 Matossian documented a schema of propositions adopted by the Assaulted Intellectual to protect his ego and expand his influence vis-à-vis the west, which immediately feel familiar in the current year: **These defensive postures lead the Assaulted Intellectual to adopt xenophobic, xenophilic, or a mixture of both attitudes**. Gandhi, for example, was more the xenophobe than Nehru, who expressed admiration for western culture. Ataturk adopted another common method: expressing xenophobia toward outsiders and reclassifying alien cultural imports as native Turkish innovations. Marxist-Leninists blended both, selecting certain western imports as “progressive” and discarding others as “reactionary.” In every case, the purpose of the attitude is to render the Assaulted Intellectual’s nation or people equal or superior to the west while also remaining to some extent apart from the west. 这些防御性姿态导致受攻击的知识分子采取仇外、亲外的态度,或两者的混合态度。例如,甘地比尼赫鲁更具有排外性,尼赫鲁对西方文化表示敬佩。阿塔图尔克采用了另一种常见的方法:对外来者表示排外,并将外来的文化进口重新归类为土耳其本土的创新。马克思列宁主义者混合了这两种方法,选择某些西方进口文化作为 "进步的",并将其他文化作为 "反动的 "予以抛弃。在每一种情况下,这种态度的目的是使受攻击的知识分子的国家或民族与西方平等或优越,同时也在一定程度上与西方保持距离。 A related ideological trait emerges through archaism, by which the assaulted intellectual selects a gilded age, like the slavophile’s interest in the peasant mir and orthodox christian practices, the Shintoist revival of emperor worship, the resurrection of Confucious in China, and Gandhi’s return to the Rama Raj. Matossian identified a more subtle example of archaism in Marxism that explained the ideology’s success in the developing world and failure in the west. Marxism, according to Matossian, appeals to the peasant’s nostalgia for the gilded age of the village ruled by the patriarch and nature, and promises that the harsh edifice of industrial society will “wither away.” **Lacking large peasant classes, Marxism failed to find a large welcoming audience in advanced western countries.** 一个相关的意识形态特征是通过古板主义出现的,被攻击的知识分子通过古板主义选择一个镀金的时代,比如斯拉夫主义者对农民奇迹和正统基督教习俗的兴趣,神道教对皇帝崇拜的复兴,孔子在中国的复活,以及甘地对拉玛王朝的回归。Matossian在马克思主义中发现了一个更微妙的陈旧主义的例子,解释了该意识形态在发展中世界的成功和在西方的失败。马托西恩认为,马克思主义呼吁农民怀念由族长和大自然统治的村庄的镀金时代,并承诺工业社会的严酷大厦将 "枯萎"。由于缺乏庞大的农民阶级,马克思主义在先进的西方国家没能找到大量欢迎的听众。 Given the importance of archaism for the assaulted intellectual, it follows that ideologies of delayed industrialisation must exhibit a tension between archaism and the power of western futurism. The most explicit example, addressed in a recent article to which I’ll return below, can be found in Italian fascism, where a golden age of Rome is recovered simultaneously alongside a call for the abandonment of tradition in favor of a new, technologically sophisticated futurism. 鉴于古老主义对受攻击的知识分子的重要性,延迟工业化的意识形态必须在古老主义和西方未来主义的力量之间表现出一种张力。在意大利法西斯主义中可以找到最明确的例子,在最近的一篇文章中,我将回到这里,在那里,罗马的黄金时代被恢复,同时呼吁放弃传统,支持一个新的、技术上先进的未来主义。 **The assaulted intellectual here is tasked with picking a gilded age that is strong and not just idyllic or simple, because he needs his culture to be superior to the west in both theory and fact.** Thus the past is often used to sanction the adoption of western innovations. In advocating for the adoption of western social mores, Kemalists claimed that in ancient times they behaved just like English gentlemen, before they were corrupted by uncouth Arab interlopers. Here Matossian identifies “manifest” archaic ideological content utilized to enforce “latent” futuristic ideology. 在这里,被攻击的知识分子的任务是挑选一个强大的、而不仅仅是田园诗般的或简单的黄金时代,因为他需要他的文化在理论和事实上都比西方优越。因此,过去常常被用来认可对西方创新的采用。凯末尔主义者在主张采用西方的社会风俗时,声称在古代他们的行为就像英国绅士一样,在他们被粗野的阿拉伯插班生腐蚀之前。在这里,马托西恩确定了 "明显的 "古老的意识形态内容,用来执行 "潜在的 "未来主义意识形态。 Reflexively, the embarrassing aspects of traditional culture – the uncouth Arab elements for Kemalists – must be purged if the Assaulted Intellectual is to solve his problem of weakness. Thus, while discovering that his people once had great virtues in the past, he simultaneously publishes research that exposes undesirable elements in his culture as foreign, or in the jargon of Marxism, as “reactionary” imports. Russian Marxists, Matossian notes, held that the preservation of peasant vernacular language over foreign and high vernaculars was “progressive,” but also championed Peter the Great as “progressive” and denounced Dostoevsky as “reactionary.” 反射性地,如果被攻击的知识分子要解决他的软弱问题,就必须清除传统文化中令人尴尬的方面--对凯末尔主义者来说就是不雅的阿拉伯元素。因此,在发现他的人民在过去曾有过伟大的美德的同时,他同时发表研究报告,揭露他的文化中的不良因素是外来的,或者用马克思主义的行话说,是 "反动 "的进口。马托西恩指出,俄国马克思主义者认为,保留农民的白话文而不是外国的和高级的白话文是 "进步的",但也把彼得大帝拥护为 "进步的",并谴责陀思妥耶夫斯基是 "反动的"。 Finally, the Assaulted Intellectual has to confront the reality of his own people – his subjects. Once again, his position is often ambiguous in this respect. Matossian asserts that “**he looks up to ‘the people’ and down on ‘the masses’.**” The people, like the past, might possess great virtues, but they are plagued by unacceptable backwardness. The Assaulted Intellectual knows that because of their backwardness, the people can never elevate the nation above the west. To get the people organized so that he can impose his will on the west, the Assaulted Intellectual needs to provide the people with doses of flattery and criticism, to offer them an “emotional New Deal” so that they will join him in battle with the west. 最后,被攻击的知识分子不得不面对他自己的人民--他的臣民的现实。再一次,他在这方面的立场往往是模糊的。Matossian断言,"他仰视'人民',俯视'群众'"。人民,就像过去一样,可能拥有伟大的美德,但他们被不可接受的落后所困扰。被攻击的知识分子知道,由于他们的落后性,人民永远无法将国家提升到西方之上。为了把人民组织起来,以便把他的意志强加给西方,受攻击的知识分子需要向人民提供奉承和批评的剂量,向他们提供 "情感新政",使他们加入他与西方的战斗。 Thus an ideology of delayed industrialization often ends up demanding that the masses accept unequal positions and unequal rewards relative to the Assaulted Intellectual. In attempting to assuage his assaulted ego, and in attempting to gain superiority over the west, the Assaulted Intellectual often ends up condemning the people to hardship and servitude and reinforcing his already superior institutional position over the masses. 因此,延迟工业化的意识形态最终往往要求大众接受相对于受攻击的知识分子的不平等地位和不平等回报。为了安抚被攻击的自我,为了获得对西方的优越感,被攻击的知识分子最终往往会让人民陷入苦难和奴役之中,并强化他在体制上对群众的优势地位。 In hindsight, the subjects of these nations may have been able to avoid a lot of pain and suffering by asking basic questions about why they endured a low standard of living for centuries before the rise of the west, why their intellectuals denounced the west after receiving western education, and why the new ideologies demand that they materially and spiritually subordinate themselves to these intellectuals. 事后看来,这些国家的臣民也许可以通过问一些基本问题来避免很多痛苦和折磨,比如为什么他们在西方崛起之前的几个世纪里忍受着低下的生活水平,为什么他们的知识分子在接受西方教育之后会谴责西方,以及为什么新的意识形态要求他们在物质和精神上服从这些知识分子的管理。 People can collude with others of like mind across the world and criticize the claims of any institution. Upstart academies and fora with high cultural and argot barriers to entry for the old power elite can be found everywhere; and ideologies can be evaluated and changed by individuals just as easily as I change a pair of pants in the bathroom during court. 人们可以与世界各地志同道合的人勾结在一起,批评任何机构的主张。新兴的学院和论坛,对旧的权力精英来说,有很高的文化和论证障碍;意识形态可以被个人评估和改变,就像我在法庭上在厕所里换一条裤子一样容易。 Access to information, particularly academic and scientific knowledge, has been broadly democratized, reducing the monopolies once enjoyed by the power elite. Graphic evidence of war and brutality is available for all, unmediated by Hollywood or the press. 获取信息,特别是学术和科学知识,已经广泛地民主化,减少了曾经由权力精英享有的垄断。所有人都可以获得战争和暴行的图片证据,不受好莱坞或媒体的影响。 The **passive**, **pseudonymous** nature of internet communication opens up a space fit for the spirit of sexless, jobless, introverted men. Their energy is unlocked for something besides war, petty criminality, or jobs in government, for which it traditionally has been reserved. Normal people living outside of the cultural and economic epicenters of the world are similarly empowered to reverse the unidirectional distribution of culture and technology. 互联网通信的被动性、假名性为无性、无工作、内向的男人们开辟了一个适合其精神的空间。他们的能量被释放出来,除了战争、轻微的犯罪行为或政府工作之外,他们的能量在传统上一直被保留。生活在世界文化和经济中心之外的普通人也同样有能力扭转文化和技术的单向分布。 ... These factors and more allow powerless and previously marginalized individuals and institutions to challenge their traditional oppressors and competitors. Thus the new space has seen the development of a culture of hyper transgressivism, incivility, and confrontation under which the prestige, authority, and values of traditional cultural institutions like the academy and press are being eroded. Indeed, the journalist of yore encounters an alien world on the internet today with millions of socially, philosophically, and technically sophisticated challengers. The many layers of irony and nihilism, and the courage to confront terrifying taboos, shocks and scandalizes them. Instead of jazz, rap, sexual liberation, and air conditioning, we have 4chan crowdsourcing military targets for Assad, democratized irony, and NEETS earning 25,000% returns by algorithmically trading shitcoins. 这些因素以及更多的因素使无权无势和以前被边缘化的个人和机构能够挑战他们的传统压迫者和竞争对手。因此,在新的空间里,出现了一种超级越轨主义、不文明和对抗的文化,在这种文化下,学术界和新闻界等传统文化机构的威望、权威和价值正在被削弱。事实上,过去的记者在今天的互联网上遇到了一个陌生的世界,有数以百万计的社会上、哲学上和技术上成熟的挑战者。许多层次的讽刺和虚无主义,以及面对可怕的禁忌的勇气,使他们感到震惊和丑恶。我们没有爵士乐、说唱、性解放和空调,而是有4chan为阿萨德众筹军事目标、民主化的讽刺,以及NEETS通过算法交易屎币赚取25000%的回报。 .... The openness of the new space is by no means a foregone conclusion. Like the Chinese destroying their Treasure Fleet armada, which had opened up vast new spaces for Chinese activity, pioneering companies like Google are partnering with old institutions of power to destroy the freedom of our new space. These enclosures are, and will be, accompanied by explicit and implicit ideological justifications. Make sure you have the right mindset to navigate them.