The Golden Age of Copy-Paste

Posted on · 4 minute read

I’ve been copy-pasting a lot of code recently. Not in the copy-from-stack-overflow-without-understanding-what-it-does kind of way, but in the deliberate, this-is-better-than-adding-a-dependency kind of way.

We use libraries because it’s less work than writing something ourselves. Someone else writes the code, we pull in a dependency, and we accept all the baggage that comes with it - churn, breaking changes, transitive dependencies, supply-chain attacks. We do because dealing with all of that is still less work than doing things in-house.

But recently the balance has shifted. AI has made the cost of writing code almost negligible. The cost of maintaining dependencies, on the other hand, hasn’t changed one bit.

That cost goes away if the code is mine. No version conflicts because there are no versions. No transitive dependencies. If I need something changed or fixed, I can do that right now, in my editor, without opening an issue and hoping someone gets to it. If there are things I no longer need I can rip them out. I have full control.

In the past, I’d vendor dependencies if I really needed to, but now I can do something better. I point an agent at a library and say “take their approach and adapt it for our project.” The agent reads the source, understands the patterns, strips out what I don’t need and adds what I do. It gives me code that fits my project like a glove.

It’s copy-paste, but on steroids.

Copy-Paste in Practice

Here’s an example. I use Lucide Icons in my projects a lot. For server-side-rendered pages in a Rails app, the simplest way to work with those icons is to download them to an assets folder. Now, there’s Rails Icons. I could use that and pay the dependency tax. Or I could tell my agent to copy-paste exactly what I need.

Take a look at the Rails Icons library: https://github.com/Rails-Designer/rails_icons. Copy the approach the gem takes to sync Lucide icons. Also copy and simplify the icon helper it provides.

# lib/tasks/icons.rake
require "open-uri"
require "fileutils"

namespace :icons do
  desc "Sync Lucide icons - download missing, remove unused"
  task sync: :environment do
    icons_dir = Rails.root.join("app/assets/images/icons")
    config_path = Rails.root.join("config/icons.yml")
    lucide_cdn = "https://unpkg.com/lucide-static/icons"

    config = YAML.load_file(config_path)
    icons = config["icons"].map(&:to_s)

    FileUtils.mkdir_p(icons_dir)

    icons.each do |icon_name|
      local_path = icons_dir.join("#{icon_name}.svg")
      next if File.exist?(local_path)

      url = "#{lucide_cdn}/#{icon_name}.svg"

      print "Downloading #{icon_name}..."
      svg = URI.open(url).read
      File.write(local_path, svg)
      puts " done"
    rescue OpenURI::HTTPError => e
      puts " failed (#{e.message})"
    end

    existing_files = Dir.glob(icons_dir.join("*.svg"))
    existing_files.each do |file_path|
      icon_name = File.basename(file_path, ".svg")
      next if icons.include?(icon_name)

      puts "Removing #{icon_name}.svg (not in config)"
      File.delete(file_path)
    end

    puts "Synced #{icons.size} icons."
  end
end
# app/helpers/icon_helper.rb

require "nokogiri"

module IconHelper
  @svg_cache = {}

  class << self
    attr_reader :svg_cache

    def clear_cache
      @svg_cache.clear
    end
  end

  def icon(name, **options)
    cache_key = [name, options].hash

    IconHelper.svg_cache[cache_key] ||= begin
      icon_path = Rails.root.join("app", "assets", "images", "icons", "#{name}.svg")
      raise "Icon #{name} not found at #{icon_path}" unless File.exist?(icon_path)

      dot_svg = Nokogiri::HTML::DocumentFragment.parse(File.read(icon_path)).at_css("svg")

      options.each do |key, value|
        if key == :class
          dot_svg[:class] = ActionController::Base.helpers.token_list(dot_svg[:class], value)
        else
          dot_svg[key.to_s.tr("_", "-")] = value
        end
      end

      dot_svg.to_html.html_safe
    end
  end
end

That’s all it is. Two files that do exactly what I need. And that’s just one example. I’ve done this for Svelte Maplibre, Svelte Bottom Sheet, Debounce, a bunch of StimulusJS controllers, and a metric ton of gems. I barely touch package.json or my Gemfile anymore.

This Is Not New

I hear what you’re saying. I could do all of this years ago. Vendoring dependencies has been around forever - Rails has had vendor/ from the start! Forking is as old as open source itself. Projects like shadcn are already spearheading the copy-into-your-project pattern, choosing local ownership over package management. So, what’s new?

What’s new is the ease with which I can understand and reshape the source. I can strip out the bloated plugin system I don’t need, rename things to match my conventions, and add the feature the library author rejected in that GitHub issue from 2021. Thanks, Anthropic/OpenAI/Gemini!

When To Copy-Paste

That said, not everything is fair game. If the agent can distill a library down to a handful of files I can read and understand in one sitting, it’s a good candidate. UI components are delicious copy-pasta, as are most utility libraries. They’re well-scoped and easy to reason about.

If I’d need a week to wrap my head around the internals, I stick to the tried-and-true dependency. I’m not going to copy-paste a cryptography library or an authentication framework. The whole point of using established libraries for those concerns is that many eyes are watching for bugs, and the risk of screwing something up in the process of adapting them just isn’t worth it.

Where Does It End?

People have been pronouncing the end of SaaS — why buy software if AI can build it for you? I don’t necessarily agree. But if that reasoning holds, might something similar happen to the open-source library ecosystem?

If everyone vendors everything and nobody contributes back upstream, libraries rot. No bug reports, no pull requests, no improvements — and ultimately, nothing worth copying anymore. There’s a tragedy of the commons here. Copy-pasting depends on high-quality libraries existing, but it’s also corrosive to the ecosystem that makes them flourish.

I don’t think libraries are going away, though. Their role will shift. From dependency-you-pull-in-and-use to reference implementation. In a way, that’s always been part of what libraries were, but it could become the primary value rather than a side effect. Not something to depend on — but a foundation for my own implementation.

I don’t know how the ecosystem will adapt, or whether it’ll flourish or suffer. What I do know is that right now, I can take the best ideas from any open-source library and make them mine in minutes.

It’s a great time to copy-paste.